Iterate over realms asynchonously
This commit moves the iteration of realms for authentication and user lookup to be done in an asynchronous fashion. The existing blocking methods have been deprecated to allow custom realm implementors time to switch. All internal realms implement the asynchronous methods. This PR is another step toward the full migration to async authentication, but does not complete the work. Additional work is needed for the LDAP realms, which make blocking network calls. These blocking calls will be handled in a follow-up PR. See elastic/elasticsearch#3790 Original commit: elastic/x-pack-elasticsearch@a65a9b2bb4
This commit is contained in:
parent
f265ab7cae
commit
637154cc6e
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.common;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This action listener wraps another listener and provides a framework for iteration over a List while calling an asynchronous function
|
||||
* for each. The listener calls the {@link BiConsumer} with the current element in the list and a {@link ActionListener}. This function
|
||||
* is expected to call the listener in case of success or failure due to an exception. If there is a failure due to an exception the wrapped
|
||||
* listener's {@link ActionListener#onFailure(Exception)} method is called. If the consumer calls {@link #onResponse(Object)} with a
|
||||
* non-null object, iteration will cease and the wrapped listener will be called with the response. In the case of a null value being passed
|
||||
* to {@link #onResponse(Object)} then iteration will continue by applying the {@link BiConsumer} to the next item in the list; if the list
|
||||
* has no more elements then the wrapped listener will be called with a null value.
|
||||
*
|
||||
* After creation, iteration is started by calling {@link #run()}
|
||||
*/
|
||||
public final class IteratingActionListener<T, U> implements ActionListener<T>, Runnable {
|
||||
|
||||
private final List<U> consumables;
|
||||
private final ActionListener<T> delegate;
|
||||
private final BiConsumer<U, ActionListener<T>> consumer;
|
||||
|
||||
private int position = 0;
|
||||
|
||||
public IteratingActionListener(ActionListener<T> delegate, BiConsumer<U, ActionListener<T>> consumer, List<U> consumables) {
|
||||
this.delegate = delegate;
|
||||
this.consumer = consumer;
|
||||
this.consumables = Collections.unmodifiableList(consumables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (consumables.isEmpty()) {
|
||||
onResponse(null);
|
||||
} else if (position < 0 || position >= consumables.size()) {
|
||||
onFailure(new IllegalStateException("invalid position [" + position + "]. List size [" + consumables.size() + "]"));
|
||||
} else {
|
||||
consumer.accept(consumables.get(position++), this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(T response) {
|
||||
if (response == null) {
|
||||
if (position == consumables.size()) {
|
||||
delegate.onResponse(null);
|
||||
} else {
|
||||
consumer.accept(consumables.get(position++), this);
|
||||
}
|
||||
} else {
|
||||
delegate.onResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
delegate.onFailure(e);
|
||||
}
|
||||
}
|
|
@ -92,6 +92,7 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
reservedRealm.lookupUser(user, realmGroupListener);
|
||||
}
|
||||
}
|
||||
|
||||
// user store lookups
|
||||
if (specificUsersRequested && usersToSearchFor.isEmpty()) {
|
||||
groupListener.onResponse(Collections.emptyList()); // no users requested notify
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.elasticsearch.node.Node;
|
|||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportMessage;
|
||||
import org.elasticsearch.xpack.common.IteratingActionListener;
|
||||
import org.elasticsearch.xpack.security.audit.AuditTrail;
|
||||
import org.elasticsearch.xpack.security.audit.AuditTrailService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication.RealmRef;
|
||||
|
@ -29,6 +30,7 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.elasticsearch.xpack.security.Security.setting;
|
||||
|
@ -233,19 +235,32 @@ public class AuthenticationService extends AbstractComponent {
|
|||
handleNullToken();
|
||||
} else {
|
||||
authenticationToken = token;
|
||||
Runnable action = () -> consumeUser(null);
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
User user = authenticateToken(realm);
|
||||
if (user != null) {
|
||||
action = () -> consumeUser(user);
|
||||
break;
|
||||
}
|
||||
final List<Realm> realmsList = realms.asList();
|
||||
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 {
|
||||
// user was authenticated, populate the authenticated by information
|
||||
authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
}
|
||||
userListener.onResponse(user);
|
||||
}, userListener::onFailure));
|
||||
} else {
|
||||
userListener.onResponse(null);
|
||||
}
|
||||
};
|
||||
final IteratingActionListener<User, Realm> authenticatingListener =
|
||||
new IteratingActionListener<>(ActionListener.wrap(this::consumeUser,
|
||||
(e) -> listener.onFailure(request.exceptionProcessingRequest(e, token))),
|
||||
realmAuthenticatingConsumer, realmsList);
|
||||
try {
|
||||
authenticatingListener.run();
|
||||
} catch (Exception e) {
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, token));
|
||||
listener.onFailure(request.exceptionProcessingRequest(e, token));
|
||||
}
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,24 +308,6 @@ public class AuthenticationService extends AbstractComponent {
|
|||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the interaction with the realm and audit trail when attempting to authenticate a token. If the realm supports the
|
||||
* token, authentication will be attempted. A successful authentication results in returning a non-null user in addition to setting
|
||||
* the authenticatedBy value. A failed authentication will result in returning {@code null}
|
||||
*/
|
||||
private User authenticateToken(Realm realm) {
|
||||
User user = null;
|
||||
if (realm.supports(authenticationToken)) {
|
||||
user = realm.authenticate(authenticationToken);
|
||||
if (user == null) {
|
||||
request.realmAuthenticationFailed(authenticationToken, realm.name());
|
||||
} else {
|
||||
authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the {@link User} that resulted from attempting to authenticate a token against the {@link Realms}. When the user is
|
||||
* {@code null}, authentication fails and does not proceed. When there is a user, the request is inspected to see if the run as
|
||||
|
@ -345,51 +342,31 @@ public class AuthenticationService extends AbstractComponent {
|
|||
* names of users that exist using a timing attack
|
||||
*/
|
||||
private void lookupRunAsUser(final User user, String runAsUsername, Consumer<User> userConsumer) {
|
||||
// FIXME there are certain actions that could be allowed now with the default role that we should probably bail on!
|
||||
Runnable action = () -> {
|
||||
if (lookedupBy != null) {
|
||||
// the requested run as user does not exist, but we don't throw an error here otherwise this could let
|
||||
// information leak about users in the system... instead we'll just let the authz service fail throw an
|
||||
// authorization error
|
||||
if (lookedupBy != null) {
|
||||
throw new IllegalStateException("we could not lookup the user but created a realm reference");
|
||||
}
|
||||
final List<Realm> realmsList = realms.asList();
|
||||
final BiConsumer<Realm, ActionListener<User>> realmLookupConsumer = (realm, lookupUserListener) -> {
|
||||
if (realm.userLookupSupported()) {
|
||||
realm.lookupUser(runAsUsername, ActionListener.wrap((lookedupUser) -> {
|
||||
if (lookedupUser != null) {
|
||||
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
lookupUserListener.onResponse(lookedupUser);
|
||||
} else {
|
||||
lookupUserListener.onResponse(null);
|
||||
}
|
||||
}, lookupUserListener::onFailure));
|
||||
} else {
|
||||
userConsumer.accept(new User(user, new User(runAsUsername, Strings.EMPTY_ARRAY)));
|
||||
lookupUserListener.onResponse(null);
|
||||
}
|
||||
};
|
||||
|
||||
final IteratingActionListener<User, Realm> userLookupListener =
|
||||
new IteratingActionListener<>(ActionListener.wrap((lookupUser) -> userConsumer.accept(new User(user, lookupUser)),
|
||||
(e) -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken))),
|
||||
realmLookupConsumer, realmsList);
|
||||
try {
|
||||
for (Realm realm : realms) {
|
||||
User runAsUser = lookupUser(realm, runAsUsername);
|
||||
if (runAsUser != null) {
|
||||
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
action = () -> userConsumer.accept(new User(user, runAsUser));
|
||||
break;
|
||||
}
|
||||
}
|
||||
userLookupListener.run();
|
||||
} catch (Exception e) {
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
|
||||
listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken));
|
||||
}
|
||||
|
||||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing when
|
||||
// an exception bubbles up even after successful authentication
|
||||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the interaction with the realm and trying to lookup a user. If a user is found, this method also creates the
|
||||
* {@link RealmRef} that identifies the realm that looked up the user
|
||||
*/
|
||||
private User lookupUser(Realm realm, String runAsUsername) {
|
||||
User lookedUp = null;
|
||||
if (realm.userLookupSupported()) {
|
||||
lookedUp = realm.lookupUser(runAsUsername);
|
||||
if (lookedUp != null) {
|
||||
lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName);
|
||||
}
|
||||
}
|
||||
return lookedUp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -71,25 +71,43 @@ public abstract class Realm implements Comparable<Realm> {
|
|||
public abstract AuthenticationToken token(ThreadContext context);
|
||||
|
||||
/**
|
||||
* Authenticates the given token. A successful authentication will return the User associated
|
||||
* with the given token. An unsuccessful authentication returns {@code null}.
|
||||
* Authenticates the given token in a blocking fashion. A successful authentication will return the User associated
|
||||
* with the given token. An unsuccessful authentication returns {@code null}. This method is deprecated in favor of
|
||||
* {@link #authenticate(AuthenticationToken, ActionListener)}.
|
||||
*
|
||||
* @param token The authentication token
|
||||
* @return The authenticated user or {@code null} if authentication failed.
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract User authenticate(AuthenticationToken token);
|
||||
|
||||
public void authenticate(AuthenticationToken token, ActionListener<User> listener) {
|
||||
try {
|
||||
listener.onResponse(authenticate(token));
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the user identified the String identifier. A successful lookup will return the {@link User} identified
|
||||
* by the username. An unsuccessful lookup returns {@code null}.
|
||||
* by the username. An unsuccessful lookup returns {@code null}. This method is deprecated in favor of
|
||||
* {@link #lookupUser(String, ActionListener)}
|
||||
*
|
||||
* @param username the String identifier for the user
|
||||
* @return the {@link User} or {@code null} if lookup failed
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract User lookupUser(String username);
|
||||
|
||||
public void lookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onResponse(lookupUser(username));
|
||||
try {
|
||||
User user = lookupUser(username);
|
||||
listener.onResponse(user);
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
|
@ -100,9 +118,11 @@ public abstract class Realm implements Comparable<Realm> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this realm supports user lookup.
|
||||
* Indicates whether this realm supports user lookup. This method is deprecated. In the future if lookup is not supported, simply
|
||||
* return null when called.
|
||||
* @return true if the realm supports user lookup
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract boolean userLookupSupported();
|
||||
|
||||
@Override
|
||||
|
|
|
@ -124,6 +124,24 @@ public class Realms extends AbstractLifecycleComponent implements Iterable<Realm
|
|||
}
|
||||
}
|
||||
|
||||
public List<Realm> asList() {
|
||||
if (licenseState.isAuthAllowed() == false) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
AllowedRealmType allowedRealmType = licenseState.allowedRealmType();
|
||||
switch (allowedRealmType) {
|
||||
case ALL:
|
||||
return Collections.unmodifiableList(realms);
|
||||
case DEFAULT:
|
||||
return Collections.unmodifiableList(internalRealmsOnly);
|
||||
case NATIVE:
|
||||
return Collections.unmodifiableList(nativeRealmsOnly);
|
||||
default:
|
||||
throw new IllegalStateException("authentication should not be enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public Realm realm(String name) {
|
||||
for (Realm realm : realms) {
|
||||
if (name.equals(realm.config.name)) {
|
||||
|
|
|
@ -30,19 +30,13 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
return userStore.getUser(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
userStore.getUsers(new String[] {username}, ActionListener.wrap(c -> listener.onResponse(c.stream().findAny().orElse(null)),
|
||||
listener::onFailure));
|
||||
userStore.getUser(username, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return userStore.verifyPassword(token.principal(), token.credentials());
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
userStore.verifyPassword(token.principal(), token.credentials(), listener);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -108,13 +107,15 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
/**
|
||||
* Blocking version of {@code getUser} that blocks until the User is returned
|
||||
*/
|
||||
public User getUser(String username) {
|
||||
public void getUser(String username, ActionListener<User> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
logger.trace("attempted to get user [{}] before service was started", username);
|
||||
return null;
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
getUserAndPassword(username, ActionListener.wrap((uap) -> {
|
||||
listener.onResponse(uap == null ? null : uap.user());
|
||||
}, listener::onFailure));
|
||||
}
|
||||
UserAndPassword uap = getUserAndPassword(username);
|
||||
return uap == null ? null : uap.user();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -586,22 +587,22 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
*
|
||||
* @param username username to lookup the user by
|
||||
* @param password the plaintext password to verify
|
||||
* @return {@link} User object if successful or {@code null} if verification fails
|
||||
*/
|
||||
User verifyPassword(String username, final SecuredString password) {
|
||||
void verifyPassword(String username, final SecuredString password, ActionListener<User> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
logger.trace("attempted to verify user credentials for [{}] but service was not started", username);
|
||||
return null;
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
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());
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
UserAndPassword user = getUserAndPassword(username);
|
||||
if (user == null || user.passwordHash() == null) {
|
||||
return null;
|
||||
}
|
||||
if (hasher.verify(password, user.passwordHash())) {
|
||||
return user.user();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean started() {
|
||||
|
@ -612,14 +613,11 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
return securityIndexExists;
|
||||
}
|
||||
|
||||
ReservedUserInfo getReservedUserInfo(String username) throws Exception {
|
||||
void getReservedUserInfo(String username, ActionListener<ReservedUserInfo> listener) {
|
||||
assert started();
|
||||
final AtomicReference<ReservedUserInfo> userInfoRef = new AtomicReference<>();
|
||||
final AtomicReference<Exception> failure = new AtomicReference<>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username)
|
||||
.execute(new LatchedActionListener<>(new ActionListener<GetResponse>() {
|
||||
.execute(new ActionListener<GetResponse>() {
|
||||
@Override
|
||||
public void onResponse(GetResponse getResponse) {
|
||||
if (getResponse.isExists()) {
|
||||
|
@ -627,12 +625,14 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
||||
Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName());
|
||||
if (password == null || password.isEmpty()) {
|
||||
failure.set(new IllegalStateException("password hash must not be empty!"));
|
||||
listener.onFailure(new IllegalStateException("password hash must not be empty!"));
|
||||
} else if (enabled == null) {
|
||||
failure.set(new IllegalStateException("enabled must not be null!"));
|
||||
listener.onFailure(new IllegalStateException("enabled must not be null!"));
|
||||
} else {
|
||||
userInfoRef.set(new ReservedUserInfo(password.toCharArray(), enabled));
|
||||
listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled));
|
||||
}
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,30 +641,15 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL
|
|||
if (e instanceof IndexNotFoundException) {
|
||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage(
|
||||
"could not retrieve built in user [{}] info since security index does not exist", username), e);
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
logger.error(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"failed to retrieve built in user [{}] info", username), e);
|
||||
failure.set(e);
|
||||
listener.onFailure(null);
|
||||
}
|
||||
}
|
||||
}, latch));
|
||||
|
||||
try {
|
||||
final boolean responseReceived = latch.await(30, TimeUnit.SECONDS);
|
||||
if (responseReceived == false) {
|
||||
failure.set(new TimeoutException("timed out trying to get built in user [" + username + "]"));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
failure.set(e);
|
||||
}
|
||||
|
||||
Exception failureCause = failure.get();
|
||||
if (failureCause != null) {
|
||||
// if there is any sort of failure we need to throw an exception to prevent the fallback to the default password...
|
||||
throw failureCause;
|
||||
}
|
||||
return userInfoRef.get();
|
||||
});
|
||||
}
|
||||
|
||||
void getAllReservedUserInfo(ActionListener<Map<String, ReservedUserInfo>> listener) {
|
||||
|
|
|
@ -28,7 +28,6 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A realm for predefined users. These users can only be modified in terms of changing their passwords; no other modifications are allowed.
|
||||
|
@ -54,53 +53,59 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
if (enabled == false) {
|
||||
return null;
|
||||
}
|
||||
if (isReserved(token.principal(), config.globalSettings()) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ReservedUserInfo userInfo = getUserInfo(token.principal());
|
||||
if (userInfo != null) {
|
||||
try {
|
||||
if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
|
||||
return getUser(token.principal(), userInfo);
|
||||
listener.onResponse(null);
|
||||
} else if (isReserved(token.principal(), config.globalSettings()) == false) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
getUserInfo(token.principal(), ActionListener.wrap((userInfo) -> {
|
||||
Runnable action;
|
||||
if (userInfo != null) {
|
||||
try {
|
||||
if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
|
||||
final User user = getUser(token.principal(), userInfo);
|
||||
action = () -> listener.onResponse(user);
|
||||
} else {
|
||||
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
|
||||
token.principal()));
|
||||
}
|
||||
} finally {
|
||||
if (userInfo.passwordHash != DEFAULT_PASSWORD_HASH) {
|
||||
Arrays.fill(userInfo.passwordHash, (char) 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
|
||||
token.principal()));
|
||||
}
|
||||
} finally {
|
||||
if (userInfo.passwordHash != DEFAULT_PASSWORD_HASH) {
|
||||
Arrays.fill(userInfo.passwordHash, (char) 0);
|
||||
}
|
||||
}
|
||||
// we want the finally block to clear out the chars before we proceed further so we execute the action here
|
||||
action.run();
|
||||
}, listener::onFailure));
|
||||
}
|
||||
// this was a reserved username - don't allow this to go to another realm...
|
||||
throw Exceptions.authenticationError("failed to authenticate user [{}]", token.principal());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
if (enabled == false) {
|
||||
if (anonymousEnabled && AnonymousUser.isAnonymousUsername(username, config.globalSettings())) {
|
||||
return anonymousUser;
|
||||
listener.onResponse(anonymousUser);
|
||||
}
|
||||
return null;
|
||||
listener.onResponse(null);
|
||||
} else if (isReserved(username, config.globalSettings()) == false) {
|
||||
listener.onResponse(null);
|
||||
} else if (AnonymousUser.isAnonymousUsername(username, config.globalSettings())) {
|
||||
listener.onResponse(anonymousEnabled ? anonymousUser : null);
|
||||
} else {
|
||||
getUserInfo(username, ActionListener.wrap((userInfo) -> {
|
||||
if (userInfo != null) {
|
||||
listener.onResponse(getUser(username, userInfo));
|
||||
} else {
|
||||
// this was a reserved username - don't allow this to go to another realm...
|
||||
listener.onFailure(Exceptions.authenticationError("failed to lookup user [{}]", username));
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
if (isReserved(username, config.globalSettings()) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (AnonymousUser.isAnonymousUsername(username, config.globalSettings())) {
|
||||
return anonymousEnabled ? anonymousUser : null;
|
||||
}
|
||||
|
||||
final ReservedUserInfo userInfo = getUserInfo(username);
|
||||
if (userInfo != null) {
|
||||
return getUser(username, userInfo);
|
||||
}
|
||||
// this was a reserved username - don't allow this to go to another realm...
|
||||
throw Exceptions.authenticationError("failed to lookup user [{}]", username);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -156,26 +161,24 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
}
|
||||
|
||||
private ReservedUserInfo getUserInfo(final String username) {
|
||||
private void getUserInfo(final String username, ActionListener<ReservedUserInfo> listener) {
|
||||
if (nativeUsersStore.started() == false) {
|
||||
// we need to be able to check for the user store being started...
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nativeUsersStore.securityIndexExists() == false) {
|
||||
return DEFAULT_USER_INFO;
|
||||
}
|
||||
|
||||
try {
|
||||
ReservedUserInfo userInfo = nativeUsersStore.getReservedUserInfo(username);
|
||||
if (userInfo == null) {
|
||||
return DEFAULT_USER_INFO;
|
||||
}
|
||||
return userInfo;
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
(Supplier<?>) () -> new ParameterizedMessage("failed to retrieve password hash for reserved user [{}]", username), e);
|
||||
return null;
|
||||
listener.onResponse(null);
|
||||
} else if (nativeUsersStore.securityIndexExists() == false) {
|
||||
listener.onResponse(DEFAULT_USER_INFO);
|
||||
} else {
|
||||
nativeUsersStore.getReservedUserInfo(username, ActionListener.wrap((userInfo) -> {
|
||||
if (userInfo == null) {
|
||||
listener.onResponse(DEFAULT_USER_INFO);
|
||||
} else {
|
||||
listener.onResponse(userInfo);
|
||||
}
|
||||
}, (e) -> {
|
||||
logger.error((Supplier<?>) () ->
|
||||
new ParameterizedMessage("failed to retrieve password hash for reserved user [{}]", username), e);
|
||||
listener.onResponse(null);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc.file;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||
|
@ -36,22 +37,23 @@ public class FileRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
if (!userPasswdStore.verifyPassword(token.principal(), token.credentials())) {
|
||||
return null;
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
if (userPasswdStore.verifyPassword(token.principal(), token.credentials())) {
|
||||
String[] roles = userRolesStore.roles(token.principal());
|
||||
listener.onResponse(new User(token.principal(), roles));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
String[] roles = userRolesStore.roles(token.principal());
|
||||
|
||||
return new User(token.principal(), roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User doLookupUser(String username) {
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
if (userPasswdStore.userExists(username)) {
|
||||
String[] roles = userRolesStore.roles(username);
|
||||
return new User(username, roles);
|
||||
listener.onResponse(new User(username, roles));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authc.ldap.support;
|
|||
import com.unboundid.ldap.sdk.LDAPException;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||
import org.elasticsearch.xpack.security.authc.support.DnRoleMapper;
|
||||
|
@ -36,30 +37,43 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a username and password, open to ldap, retrieve groups, map to roles and build the user.
|
||||
*
|
||||
* @return User with elasticsearch roles
|
||||
* Given a username and password, open a connection to ldap, bind to authenticate, retrieve groups, map to roles and build the user.
|
||||
* This user will then be passed to the listener
|
||||
*/
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
protected final void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
// we use a runnable so that we call the listener outside of the try catch block. If we call within the try catch block and the
|
||||
// listener throws an exception then we mistakenly could continue realm authentication when we already authenticated the user and
|
||||
// there was some other issue
|
||||
Runnable action;
|
||||
try (LdapSession session = sessionFactory.session(token.principal(), token.credentials())) {
|
||||
return createUser(token.principal(), session);
|
||||
final User user = createUser(token.principal(), session);
|
||||
action = () -> listener.onResponse(user);
|
||||
} catch (Exception e) {
|
||||
logException("authentication", e, token.principal());
|
||||
return null;
|
||||
action = () -> listener.onResponse(null);
|
||||
}
|
||||
action.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User doLookupUser(String username) {
|
||||
protected final void doLookupUser(String username, ActionListener<User> listener) {
|
||||
// we use a runnable so that we call the listener outside of the try catch block. If we call within the try catch block and the
|
||||
// listener throws an exception then we mistakenly could continue realm lookup when we already found a matching user and
|
||||
// there was some other issue
|
||||
Runnable action;
|
||||
if (sessionFactory.supportsUnauthenticatedSession()) {
|
||||
try (LdapSession session = sessionFactory.unauthenticatedSession(username)) {
|
||||
return createUser(username, session);
|
||||
final User user = createUser(username, session);
|
||||
action = () -> listener.onResponse(user);
|
||||
} catch (Exception e) {
|
||||
logException("lookup", e, username);
|
||||
action = () -> listener.onResponse(null);
|
||||
}
|
||||
} else {
|
||||
action = () -> listener.onResponse(null);
|
||||
}
|
||||
return null;
|
||||
action.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger;
|
|||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
|
@ -81,13 +82,18 @@ public class PkiRealm extends Realm {
|
|||
|
||||
@Override
|
||||
public User authenticate(AuthenticationToken authToken) {
|
||||
throw new UnsupportedOperationException("internal realms do not support blocking calls");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(AuthenticationToken authToken, ActionListener<User> listener) {
|
||||
X509AuthenticationToken token = (X509AuthenticationToken)authToken;
|
||||
if (isCertificateChainTrusted(trustManager, token, logger) == false) {
|
||||
return null;
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
Set<String> roles = roleMapper.resolveRoles(token.dn(), Collections.<String>emptyList());
|
||||
listener.onResponse(new User(token.principal(), roles.toArray(new String[roles.size()])));
|
||||
}
|
||||
|
||||
Set<String> roles = roleMapper.resolveRoles(token.dn(), Collections.<String>emptyList());
|
||||
return new User(token.principal(), roles.toArray(new String[roles.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,13 +5,9 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.support;
|
||||
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.logging.log4j.util.Supplier;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.cache.Cache;
|
||||
import org.elasticsearch.common.cache.CacheBuilder;
|
||||
import org.elasticsearch.common.cache.CacheLoader;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
|
@ -19,7 +15,6 @@ import org.elasticsearch.xpack.security.user.User;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm implements CachingRealm {
|
||||
|
||||
|
@ -67,118 +62,73 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
* doAuthenticate
|
||||
*
|
||||
* @param authToken The authentication token
|
||||
* @return an authenticated user with roles
|
||||
*/
|
||||
@Override
|
||||
public final User authenticate(AuthenticationToken authToken) {
|
||||
public final void authenticate(AuthenticationToken authToken, ActionListener<User> listener) {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken)authToken;
|
||||
if (cache == null) {
|
||||
return doAuthenticate(token);
|
||||
}
|
||||
|
||||
try {
|
||||
UserWithHash userWithHash = cache.get(token.principal());
|
||||
if (userWithHash == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user not found in cache, proceeding with normal authentication");
|
||||
}
|
||||
User user = doAuthenticate(token);
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
userWithHash = new UserWithHash(user, token.credentials(), hasher);
|
||||
// it doesn't matter if we already computed it elsewhere
|
||||
cache.put(token.principal(), userWithHash);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), user.roles());
|
||||
}
|
||||
return user;
|
||||
if (cache == null) {
|
||||
doAuthenticate(token, listener);
|
||||
} else {
|
||||
authenticateWithCache(token, listener);
|
||||
}
|
||||
|
||||
final boolean hadHash = userWithHash.hasHash();
|
||||
if (hadHash) {
|
||||
if (userWithHash.verify(token.credentials())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
}
|
||||
return userWithHash.user;
|
||||
}
|
||||
}
|
||||
//this handles when a user's password has changed or the user was looked up for run as and not authenticated
|
||||
cache.invalidate(token.principal());
|
||||
User user = doAuthenticate(token);
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
userWithHash = new UserWithHash(user, token.credentials(), hasher);
|
||||
// it doesn't matter if we already computed it elsewhere
|
||||
cache.put(token.principal(), userWithHash);
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (hadHash) {
|
||||
logger.debug("cached user's password changed. authenticated user [{}], with roles [{}]", token.principal(),
|
||||
userWithHash.user.roles());
|
||||
} else {
|
||||
logger.debug("cached user came from a lookup and could not be used for authentication. authenticated user [{}]" +
|
||||
" with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
}
|
||||
}
|
||||
return userWithHash.user;
|
||||
|
||||
} catch (Exception ee) {
|
||||
if (ee instanceof ElasticsearchSecurityException) {
|
||||
// this should bubble out
|
||||
throw ee;
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(
|
||||
(Supplier<?>) () -> new ParameterizedMessage(
|
||||
"realm [{}] could not authenticate [{}]", type(), token.principal()), ee);
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("realm [{}] could not authenticate [{}]", type(), token.principal());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// each realm should handle exceptions, if we get one here it should be considered fatal
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final User lookupUser(final String username) {
|
||||
if (!userLookupSupported()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheLoader<String, UserWithHash> callback = key -> {
|
||||
private void authenticateWithCache(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
UserWithHash userWithHash = cache.get(token.principal());
|
||||
if (userWithHash == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("user [{}] not found in cache, proceeding with normal lookup", username);
|
||||
logger.debug("user not found in cache, proceeding with normal authentication");
|
||||
}
|
||||
User user = doLookupUser(username);
|
||||
if (user == null) {
|
||||
return null;
|
||||
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
|
||||
if (user != null) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), user.roles());
|
||||
}
|
||||
listener.onResponse(user);
|
||||
}, listener::onFailure));
|
||||
} else if (userWithHash.hasHash()) {
|
||||
if (userWithHash.verify(token.credentials())) {
|
||||
logger.debug("authenticated user [{}], with roles [{}]", token.principal(), userWithHash.user.roles());
|
||||
listener.onResponse(userWithHash.user);
|
||||
} else {
|
||||
cache.invalidate(token.principal());
|
||||
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
|
||||
if (user != null) {
|
||||
logger.debug("cached user's password changed. authenticated user [{}], with roles [{}]", token.principal(),
|
||||
user.roles());
|
||||
}
|
||||
listener.onResponse(user);
|
||||
}, listener::onFailure));
|
||||
}
|
||||
return new UserWithHash(user, null, null);
|
||||
};
|
||||
|
||||
try {
|
||||
UserWithHash userWithHash = cache.computeIfAbsent(username, callback);
|
||||
assert userWithHash != null : "the cache contract requires that a value returned from computeIfAbsent be non-null or an " +
|
||||
"ExecutionException should be thrown";
|
||||
return userWithHash.user;
|
||||
} catch (ExecutionException ee) {
|
||||
if (ee.getCause() instanceof ElasticsearchSecurityException) {
|
||||
// this should bubble out
|
||||
throw (ElasticsearchSecurityException) ee.getCause();
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace((Supplier<?>) () -> new ParameterizedMessage("realm [{}] could not lookup [{}]", name(), username), ee);
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("realm [{}] could not lookup [{}]", name(), username);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
cache.invalidate(token.principal());
|
||||
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
|
||||
if (user != null) {
|
||||
logger.debug("cached user came from a lookup and could not be used for authentication. authenticated user [{}]" +
|
||||
" with roles [{}]", token.principal(), user.roles());
|
||||
}
|
||||
listener.onResponse(user);
|
||||
}, listener::onFailure));
|
||||
}
|
||||
}
|
||||
|
||||
private void doAuthenticateAndCache(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
doAuthenticate(token, ActionListener.wrap((user) -> {
|
||||
if (user == null) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
UserWithHash userWithHash = new UserWithHash(user, token.credentials(), hasher);
|
||||
// it doesn't matter if we already computed it elsewhere
|
||||
cache.put(token.principal(), userWithHash);
|
||||
listener.onResponse(user);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> usageStats() {
|
||||
Map<String, Object> stats = super.usageStats();
|
||||
|
@ -186,54 +136,60 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
|
|||
return stats;
|
||||
}
|
||||
|
||||
protected abstract User doAuthenticate(UsernamePasswordToken token);
|
||||
protected abstract void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener);
|
||||
|
||||
@Override
|
||||
public void lookupUser(String username, ActionListener<User> listener) {
|
||||
public final void lookupUser(String username, ActionListener<User> listener) {
|
||||
if (!userLookupSupported()) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
} else if (cache != null) {
|
||||
UserWithHash withHash = cache.get(username);
|
||||
if (withHash == null) {
|
||||
doLookupUser(username, ActionListener.wrap((user) -> {
|
||||
try {
|
||||
try {
|
||||
doLookupUser(username, ActionListener.wrap((user) -> {
|
||||
Runnable action = () -> listener.onResponse(null);
|
||||
if (user != null) {
|
||||
UserWithHash userWithHash = new UserWithHash(user, null, null);
|
||||
cache.computeIfAbsent(username, (n) -> userWithHash);
|
||||
try {
|
||||
// computeIfAbsent is used here to avoid overwriting a value from a concurrent authenticate call as it
|
||||
// contains the password hash, which provides a performance boost and we shouldn't just erase that
|
||||
cache.computeIfAbsent(username, (n) -> userWithHash);
|
||||
action = () -> listener.onResponse(userWithHash.user);
|
||||
} catch (ExecutionException e) {
|
||||
action = () -> listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
listener.onResponse(user);
|
||||
} catch (ExecutionException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
action.run();
|
||||
}, listener::onFailure));
|
||||
} catch (Exception e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
} else {
|
||||
listener.onResponse(withHash.user);
|
||||
}
|
||||
} else {
|
||||
doLookupUser(username, listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract User doLookupUser(String username);
|
||||
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onResponse(doLookupUser(username));
|
||||
}
|
||||
protected abstract void doLookupUser(String username, ActionListener<User> listener);
|
||||
|
||||
private static class UserWithHash {
|
||||
User user;
|
||||
char[] hash;
|
||||
Hasher hasher;
|
||||
|
||||
public UserWithHash(User user, SecuredString password, Hasher hasher) {
|
||||
UserWithHash(User user, SecuredString password, Hasher hasher) {
|
||||
this.user = user;
|
||||
this.hash = password == null ? null : hasher.hash(password);
|
||||
this.hasher = hasher;
|
||||
}
|
||||
|
||||
public boolean verify(SecuredString password) {
|
||||
boolean verify(SecuredString password) {
|
||||
return hash != null && hasher.verify(password, hash);
|
||||
}
|
||||
|
||||
public boolean hasHash() {
|
||||
boolean hasHash() {
|
||||
return hash != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|||
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
|
||||
import org.elasticsearch.xpack.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
|
||||
public abstract class UsernamePasswordRealm extends Realm {
|
||||
abstract class UsernamePasswordRealm extends Realm {
|
||||
|
||||
public UsernamePasswordRealm(String type, RealmConfig config) {
|
||||
UsernamePasswordRealm(String type, RealmConfig config) {
|
||||
super(type, config);
|
||||
}
|
||||
|
||||
|
@ -25,4 +26,11 @@ public abstract class UsernamePasswordRealm extends Realm {
|
|||
return token instanceof UsernamePasswordToken;
|
||||
}
|
||||
|
||||
public final User authenticate(AuthenticationToken token) {
|
||||
throw new UnsupportedOperationException("internal realms should not support blocking calls!!");
|
||||
}
|
||||
|
||||
public final User lookupUser(String username) {
|
||||
throw new UnsupportedOperationException("internal realms should not support blocking calls!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.integration;
|
|||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.network.NetworkModule;
|
||||
|
@ -236,7 +237,9 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
|
|||
Map<String, Map<Realm, User>> users = new HashMap<>();
|
||||
for (Realm realm : realms) {
|
||||
for (String username : usernames) {
|
||||
User user = realm.authenticate(tokens.get(username));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(tokens.get(username), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
Map<Realm, User> realmToUser = users.get(username);
|
||||
if (realmToUser == null) {
|
||||
|
@ -251,7 +254,10 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
|
|||
|
||||
for (String username : usernames) {
|
||||
for (Realm realm : realms) {
|
||||
assertThat(realm.authenticate(tokens.get(username)), sameInstance(users.get(username).get(realm)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(tokens.get(username), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, sameInstance(users.get(username).get(realm)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,7 +267,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) {
|
||||
User user = realm.authenticate(tokens.get(username));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(tokens.get(username), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
scenario.assertEviction(users.get(username).get(realm), user);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.common;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.common.collect.HppcMaps.Object;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
|
||||
public class IteratingActionListenerTests extends ESTestCase {
|
||||
|
||||
public void testIteration() {
|
||||
final int numberOfItems = scaledRandomIntBetween(1, 32);
|
||||
final int numberOfIterations = scaledRandomIntBetween(1, numberOfItems);
|
||||
List<Object> items = new ArrayList<>(numberOfItems);
|
||||
for (int i = 0; i < numberOfItems; i++) {
|
||||
items.add(new Object());
|
||||
}
|
||||
|
||||
final AtomicInteger iterations = new AtomicInteger(0);
|
||||
final BiConsumer<Object, ActionListener<Object>> consumer = (listValue, listener) -> {
|
||||
final int current = iterations.incrementAndGet();
|
||||
if (current == numberOfIterations) {
|
||||
listener.onResponse(items.get(current - 1));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
};
|
||||
|
||||
IteratingActionListener<Object, Object> iteratingListener = new IteratingActionListener<>(ActionListener.wrap((object) -> {
|
||||
assertNotNull(object);
|
||||
assertThat(object, sameInstance(items.get(numberOfIterations - 1)));
|
||||
}, (e) -> {
|
||||
logger.error("unexpected exception", e);
|
||||
fail("exception should not have been thrown");
|
||||
}), consumer, items);
|
||||
iteratingListener.run();
|
||||
|
||||
// we never really went async, its all chained together so verify this for sanity
|
||||
assertEquals(numberOfIterations, iterations.get());
|
||||
}
|
||||
|
||||
public void testIterationEmptyList() {
|
||||
IteratingActionListener<Object, Object> listener = new IteratingActionListener<>(ActionListener.wrap(Assert::assertNull,
|
||||
(e) -> {
|
||||
logger.error("unexpected exception", e);
|
||||
fail("exception should not have been thrown");
|
||||
}), (listValue, iteratingListener) -> {
|
||||
fail("consumer should not have been called!!!");
|
||||
}, Collections.emptyList());
|
||||
listener.run();
|
||||
}
|
||||
|
||||
public void testFailure() {
|
||||
final int numberOfItems = scaledRandomIntBetween(1, 32);
|
||||
final int numberOfIterations = scaledRandomIntBetween(1, numberOfItems);
|
||||
List<Object> items = new ArrayList<>(numberOfItems);
|
||||
for (int i = 0; i < numberOfItems; i++) {
|
||||
items.add(new Object());
|
||||
}
|
||||
|
||||
final AtomicInteger iterations = new AtomicInteger(0);
|
||||
final BiConsumer<Object, ActionListener<Object>> consumer = (listValue, listener) -> {
|
||||
final int current = iterations.incrementAndGet();
|
||||
if (current == numberOfIterations) {
|
||||
listener.onFailure(new ElasticsearchException("expected exception"));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
};
|
||||
|
||||
final AtomicBoolean onFailureCalled = new AtomicBoolean(false);
|
||||
IteratingActionListener<Object, Object> iteratingListener = new IteratingActionListener<>(ActionListener.wrap((object) -> {
|
||||
fail("onResponse should not have been called, but was called with: " + object);
|
||||
}, (e) -> {
|
||||
assertEquals("expected exception", e.getMessage());
|
||||
assertTrue(onFailureCalled.compareAndSet(false, true));
|
||||
}), consumer, items);
|
||||
iteratingListener.run();
|
||||
|
||||
// we never really went async, its all chained together so verify this for sanity
|
||||
assertEquals(numberOfIterations, iterations.get());
|
||||
assertTrue(onFailureCalled.get());
|
||||
}
|
||||
}
|
|
@ -47,6 +47,8 @@ import static org.hamcrest.Matchers.nullValue;
|
|||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
|
@ -145,9 +147,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
public void testAuthenticateBothSupportSecondSucceeds() throws Exception {
|
||||
User user = new User("_username", "r1");
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(null); // first fails
|
||||
mockAuthenticate(firstRealm, token, null);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(secondRealm, token, user);
|
||||
if (randomBoolean()) {
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
} else {
|
||||
|
@ -168,7 +170,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user = new User("_username", "r1");
|
||||
when(firstRealm.supports(token)).thenReturn(false);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(secondRealm, token, user);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
|
@ -219,7 +221,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user = new User("_username", "r1");
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(firstRealm, token, user);
|
||||
|
||||
Authentication result = authenticateBlocking("_action", message, null);
|
||||
|
||||
|
@ -276,7 +278,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(firstRealm, token, user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", message, fallback));
|
||||
|
@ -289,7 +291,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user = new User("username", new String[] { "r1", "r2" }, null, null, null, false);
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(firstRealm, token, user);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking(restRequest));
|
||||
|
@ -303,7 +305,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(firstRealm, token, user);
|
||||
|
||||
Authentication result = authenticateBlocking("_action", message, fallback);
|
||||
assertThat(result, notNullValue());
|
||||
|
@ -317,7 +319,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user1 = new User("username", "r1", "r2");
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
mockAuthenticate(firstRealm, token, user1);
|
||||
Authentication result = authenticateBlocking(restRequest);
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getUser(), sameInstance(user1));
|
||||
|
@ -330,7 +332,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user1 = new User("username", "r1", "r2");
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
mockAuthenticate(firstRealm, token, user1);
|
||||
Authentication authentication = authenticateBlocking("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
|
@ -382,7 +384,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
User user1 = new User("username", "r1", "r2");
|
||||
when(firstRealm.supports(token)).thenReturn(true);
|
||||
when(firstRealm.token(threadContext)).thenReturn(token);
|
||||
when(firstRealm.authenticate(token)).thenReturn(user1);
|
||||
mockAuthenticate(firstRealm, token, user1);
|
||||
Authentication authentication = authenticateBlocking("_action", message, SystemUser.INSTANCE);
|
||||
assertThat(authentication, notNullValue());
|
||||
assertThat(authentication.getUser(), sameInstance(user1));
|
||||
|
@ -572,7 +574,8 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenThrow(authenticationError("realm doesn't like authenticate"));
|
||||
doThrow(authenticationError("realm doesn't like authenticate"))
|
||||
.when(secondRealm).authenticate(eq(token), any(ActionListener.class));
|
||||
try {
|
||||
authenticateBlocking("_action", message, null);
|
||||
fail("exception should bubble out");
|
||||
|
@ -586,7 +589,8 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenThrow(authenticationError("realm doesn't like authenticate"));
|
||||
doThrow(authenticationError("realm doesn't like authenticate"))
|
||||
.when(secondRealm).authenticate(eq(token), any(ActionListener.class));
|
||||
try {
|
||||
authenticateBlocking(restRequest);
|
||||
fail("exception should bubble out");
|
||||
|
@ -601,8 +605,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenThrow(authenticationError("realm doesn't want to lookup"));
|
||||
mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"}));
|
||||
doThrow(authenticationError("realm doesn't want to lookup"))
|
||||
.when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
|
@ -619,8 +624,9 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as")).thenThrow(authenticationError("realm doesn't want to lookup"));
|
||||
mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"}));
|
||||
doThrow(authenticationError("realm doesn't want to " + "lookup"))
|
||||
.when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
|
@ -639,8 +645,12 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
final User user = new User("lookup user", new String[]{"user"}, "lookup user", "lookup@foo.foo",
|
||||
Collections.singletonMap("foo", "bar"), true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
when(secondRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
|
||||
mockAuthenticate(secondRealm, token, user);
|
||||
doAnswer((i) -> {
|
||||
ActionListener listener = (ActionListener) i.getArguments()[1];
|
||||
listener.onResponse(new User("looked up user", new String[]{"some role"}));
|
||||
return null;
|
||||
}).when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
Authentication result;
|
||||
|
@ -671,9 +681,13 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"}));
|
||||
when(firstRealm.userLookupSupported()).thenReturn(true);
|
||||
when(firstRealm.lookupUser("run_as")).thenReturn(new User("looked up user", new String[]{"some role"}));
|
||||
doAnswer((i) -> {
|
||||
ActionListener listener = (ActionListener) i.getArguments()[1];
|
||||
listener.onResponse(new User("looked up user", new String[]{"some role"}));
|
||||
return null;
|
||||
}).when(firstRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(firstRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
Authentication result;
|
||||
|
@ -700,7 +714,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(secondRealm, token, user);
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
|
@ -718,7 +732,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(user);
|
||||
mockAuthenticate(secondRealm, token, user);
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
try {
|
||||
|
@ -735,9 +749,12 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as"))
|
||||
.thenReturn(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"}));
|
||||
doAnswer((i) -> {
|
||||
ActionListener listener = (ActionListener) i.getArguments()[1];
|
||||
listener.onResponse(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
return null;
|
||||
}).when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
User fallback = randomBoolean() ? SystemUser.INSTANCE : null;
|
||||
ElasticsearchSecurityException e =
|
||||
|
@ -752,9 +769,12 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as");
|
||||
when(secondRealm.token(threadContext)).thenReturn(token);
|
||||
when(secondRealm.supports(token)).thenReturn(true);
|
||||
when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"}));
|
||||
when(secondRealm.lookupUser("run_as"))
|
||||
.thenReturn(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"}));
|
||||
doAnswer((i) -> {
|
||||
ActionListener listener = (ActionListener) i.getArguments()[1];
|
||||
listener.onResponse(new User("looked up user", new String[]{"some role"}, null, null, null, false));
|
||||
return null;
|
||||
}).when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class));
|
||||
when(secondRealm.userLookupSupported()).thenReturn(true);
|
||||
|
||||
ElasticsearchSecurityException e =
|
||||
|
@ -782,6 +802,14 @@ 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);
|
||||
return null;
|
||||
}).when(realm).authenticate(eq(token), any(ActionListener.class));
|
||||
}
|
||||
|
||||
private Authentication authenticateBlocking(RestRequest restRequest) {
|
||||
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
|
||||
service.authenticate(restRequest, future);
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.unboundid.ldap.sdk.LDAPException;
|
|||
import com.unboundid.ldap.sdk.LDAPURL;
|
||||
import com.unboundid.ldap.sdk.schema.Schema;
|
||||
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
|
@ -116,7 +117,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null);
|
||||
ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper);
|
||||
|
||||
User user = realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.roles(), arrayContaining(containsString("Avengers")));
|
||||
}
|
||||
|
@ -129,7 +132,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper);
|
||||
|
||||
// Thor does not have a UPN of form CN=Thor@ad.test.elasticsearch.com
|
||||
User user = realm.authenticate(new UsernamePasswordToken("CN=Thor", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=Thor", SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.roles(), arrayContaining(containsString("Avengers")));
|
||||
}
|
||||
|
@ -152,7 +157,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
|
||||
int count = randomIntBetween(2, 10);
|
||||
for (int i = 0; i < count; i++) {
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
}
|
||||
|
||||
// verify one and only one session as further attempts should be returned from cache
|
||||
|
@ -168,7 +175,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
|
||||
int count = randomIntBetween(2, 10);
|
||||
for (int i = 0; i < count; i++) {
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
}
|
||||
|
||||
// verify one and only one session as second attempt should be returned from cache
|
||||
|
@ -184,7 +193,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
|
||||
int count = randomIntBetween(2, 10);
|
||||
for (int i = 0; i < count; i++) {
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
}
|
||||
|
||||
// verify one and only one session as further attempts should be returned from cache
|
||||
|
@ -194,7 +205,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
roleMapper.notifyRefresh();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
}
|
||||
|
||||
verify(sessionFactory, times(2)).session(eq("CN=ironman"), any(SecuredString.class));
|
||||
|
@ -209,7 +222,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null);
|
||||
ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper);
|
||||
|
||||
User user = realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=ironman", SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.roles(), arrayContaining(equalTo("group_role")));
|
||||
}
|
||||
|
@ -223,7 +238,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
|
|||
DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null);
|
||||
ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper);
|
||||
|
||||
User user = realm.authenticate(new UsernamePasswordToken("CN=Thor", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("CN=Thor", SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder(equalTo("group_role"), equalTo("user_role")));
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Before;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
|
@ -33,6 +34,7 @@ import static org.hamcrest.Matchers.empty;
|
|||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
@ -62,29 +64,38 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final String principal = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
|
||||
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||
() -> reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD)));
|
||||
PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD), listener);
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class, listener::actionGet);
|
||||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||
verify(usersStore).started();
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
public void testDefaultPasswordAuthentication() throws Throwable {
|
||||
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expected.principal();
|
||||
final boolean securityIndexExists = randomBoolean();
|
||||
if (securityIndexExists) {
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
doAnswer((i) -> {
|
||||
ActionListener listener = (ActionListener) i.getArguments()[1];
|
||||
listener.onResponse(null);
|
||||
return null;
|
||||
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
}
|
||||
final ReservedRealm reservedRealm =
|
||||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expected.principal();
|
||||
|
||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
|
||||
|
||||
PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD), listener);
|
||||
final User authenticated = listener.actionGet();
|
||||
assertEquals(expected, authenticated);
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
if (securityIndexExists) {
|
||||
verify(usersStore).getReservedUserInfo(principal);
|
||||
verify(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
}
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
@ -99,7 +110,9 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expected.principal();
|
||||
|
||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
|
||||
PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD), listener);
|
||||
final User authenticated = listener.actionGet();
|
||||
assertNull(authenticated);
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
@ -111,22 +124,33 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
final String principal = expectedUser.principal();
|
||||
final SecuredString newPassword = new SecuredString("foobar".toCharArray());
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
when(usersStore.getReservedUserInfo(principal)).thenReturn(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback = (ActionListener) i.getArguments()[1];
|
||||
callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
return null;
|
||||
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
|
||||
// test default password
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class,
|
||||
() -> reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD)));
|
||||
final PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD), listener);
|
||||
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class, listener::actionGet);
|
||||
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
|
||||
|
||||
// the realm assumes it owns the hashed password so it fills it with 0's
|
||||
when(usersStore.getReservedUserInfo(principal)).thenReturn(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback = (ActionListener) i.getArguments()[1];
|
||||
callback.onResponse(new ReservedUserInfo(Hasher.BCRYPT.hash(newPassword), true));
|
||||
return null;
|
||||
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
|
||||
// test new password
|
||||
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword));
|
||||
final PlainActionFuture<User> authListener = new PlainActionFuture<>();
|
||||
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword), authListener);
|
||||
final User authenticated = authListener.actionGet();
|
||||
assertEquals(expectedUser, authenticated);
|
||||
verify(usersStore, times(2)).started();
|
||||
verify(usersStore, times(2)).securityIndexExists();
|
||||
verify(usersStore, times(2)).getReservedUserInfo(principal);
|
||||
verify(usersStore, times(2)).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
|
@ -136,12 +160,16 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
|
||||
final User user = reservedRealm.doLookupUser(principal);
|
||||
PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doLookupUser(principal, listener);
|
||||
final User user = listener.actionGet();
|
||||
assertEquals(expectedUser, user);
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
|
||||
final User doesntExist = reservedRealm.doLookupUser("foobar");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
reservedRealm.doLookupUser("foobar", future);
|
||||
final User doesntExist = future.actionGet();
|
||||
assertThat(doesntExist, nullValue());
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
@ -153,7 +181,9 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
|
||||
final String principal = expectedUser.principal();
|
||||
|
||||
final User user = reservedRealm.doLookupUser(principal);
|
||||
PlainActionFuture<User> listener = new PlainActionFuture<>();
|
||||
reservedRealm.doLookupUser(principal, listener);
|
||||
final User user = listener.actionGet();
|
||||
assertNull(user);
|
||||
verifyZeroInteractions(usersStore);
|
||||
}
|
||||
|
@ -165,15 +195,20 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
final String principal = expectedUser.principal();
|
||||
when(usersStore.securityIndexExists()).thenReturn(true);
|
||||
final RuntimeException e = new RuntimeException("store threw");
|
||||
when(usersStore.getReservedUserInfo(principal)).thenThrow(e);
|
||||
doAnswer((i) -> {
|
||||
ActionListener callback = (ActionListener) i.getArguments()[1];
|
||||
callback.onFailure(e);
|
||||
return null;
|
||||
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
|
||||
ElasticsearchSecurityException securityException =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> reservedRealm.lookupUser(principal));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
reservedRealm.lookupUser(principal, future);
|
||||
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, future::actionGet);
|
||||
assertThat(securityException.getMessage(), containsString("failed to lookup"));
|
||||
|
||||
verify(usersStore).started();
|
||||
verify(usersStore).securityIndexExists();
|
||||
verify(usersStore).getReservedUserInfo(principal);
|
||||
verify(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
|
||||
verifyNoMoreInteractions(usersStore);
|
||||
}
|
||||
|
||||
|
@ -226,17 +261,16 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
|
||||
// maybe cache a successful auth
|
||||
if (randomBoolean()) {
|
||||
User user = reservedRealm.authenticate(
|
||||
new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("changeme".toCharArray())));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
reservedRealm.authenticate(new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("changeme".toCharArray())), future);
|
||||
User user = future.actionGet();
|
||||
assertEquals(new ElasticUser(true), user);
|
||||
}
|
||||
|
||||
try {
|
||||
reservedRealm.authenticate(new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("foobar".toCharArray())));
|
||||
fail("authentication should throw an exception otherwise we may allow others to impersonate reserved users...");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("failed to authenticate"));
|
||||
}
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
reservedRealm.authenticate(new UsernamePasswordToken(ElasticUser.NAME, new SecuredString("foobar".toCharArray())), future);
|
||||
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, future::actionGet);
|
||||
assertThat(e.getMessage(), containsString("failed to authenticate"));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -247,5 +281,12 @@ public class ReservedRealmTests extends ESTestCase {
|
|||
((ActionListener) i.getArguments()[0]).onResponse(collection);
|
||||
return null;
|
||||
}).when(usersStore).getAllReservedUserInfo(any(ActionListener.class));
|
||||
|
||||
for (Entry<String, ReservedUserInfo> entry : collection.entrySet()) {
|
||||
doAnswer((i) -> {
|
||||
((ActionListener) i.getArguments()[1]).onResponse(entry.getValue());
|
||||
return null;
|
||||
}).when(usersStore).getReservedUserInfo(eq(entry.getKey()), any(ActionListener.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.file;
|
||||
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
|
@ -50,7 +51,9 @@ public class FileRealmTests extends ESTestCase {
|
|||
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
|
||||
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings);
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
User user = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.principal(), equalTo("user1"));
|
||||
assertThat(user.roles(), notNullValue());
|
||||
|
@ -66,8 +69,12 @@ public class FileRealmTests extends ESTestCase {
|
|||
when(userPasswdStore.verifyPassword("user1", SecuredStringTests.build("test123"))).thenReturn(true);
|
||||
when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
User user1 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
User user2 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user1 = future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user2 = future.actionGet();
|
||||
assertThat(user1, sameInstance(user2));
|
||||
}
|
||||
|
||||
|
@ -78,18 +85,34 @@ public class FileRealmTests extends ESTestCase {
|
|||
doReturn(true).when(userPasswdStore).verifyPassword("user1", SecuredStringTests.build("test123"));
|
||||
doReturn(new String[] { "role1", "role2" }).when(userRolesStore).roles("user1");
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
User user1 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
User user2 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user1 = future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user2 = future.actionGet();
|
||||
assertThat(user1, sameInstance(user2));
|
||||
|
||||
userPasswdStore.notifyRefresh();
|
||||
User user3 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user3 = future.actionGet();
|
||||
assertThat(user2, not(sameInstance(user3)));
|
||||
User user4 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user4 = future.actionGet();
|
||||
assertThat(user3, sameInstance(user4));
|
||||
|
||||
userRolesStore.notifyRefresh();
|
||||
User user5 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user5 = future.actionGet();
|
||||
assertThat(user4, not(sameInstance(user5)));
|
||||
User user6 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")));
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123")), future);
|
||||
User user6 = future.actionGet();
|
||||
assertThat(user5, sameInstance(user6));
|
||||
}
|
||||
|
||||
|
@ -115,7 +138,9 @@ public class FileRealmTests extends ESTestCase {
|
|||
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings);
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
|
||||
User user = realm.lookupUser("user1");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user = future.actionGet();
|
||||
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.principal(), equalTo("user1"));
|
||||
|
@ -130,8 +155,12 @@ public class FileRealmTests extends ESTestCase {
|
|||
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings);
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
|
||||
User user = realm.lookupUser("user1");
|
||||
User user1 = realm.lookupUser("user1");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user = future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user1 = future.actionGet();
|
||||
assertThat(user, sameInstance(user1));
|
||||
verify(userPasswdStore).userExists("user1");
|
||||
verify(userRolesStore).roles("user1");
|
||||
|
@ -144,18 +173,34 @@ public class FileRealmTests extends ESTestCase {
|
|||
doReturn(true).when(userPasswdStore).userExists("user1");
|
||||
doReturn(new String[] { "role1", "role2" }).when(userRolesStore).roles("user1");
|
||||
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
|
||||
User user1 = realm.lookupUser("user1");
|
||||
User user2 = realm.lookupUser("user1");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user1 = future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user2 = future.actionGet();
|
||||
assertThat(user1, sameInstance(user2));
|
||||
|
||||
userPasswdStore.notifyRefresh();
|
||||
User user3 = realm.lookupUser("user1");
|
||||
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user3 = future.actionGet();
|
||||
assertThat(user2, not(sameInstance(user3)));
|
||||
User user4 = realm.lookupUser("user1");
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user4 = future.actionGet();
|
||||
assertThat(user3, sameInstance(user4));
|
||||
|
||||
userRolesStore.notifyRefresh();
|
||||
User user5 = realm.lookupUser("user1");
|
||||
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user5 = future.actionGet();
|
||||
assertThat(user4, not(sameInstance(user5)));
|
||||
User user6 = realm.lookupUser("user1");
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user1", future);
|
||||
User user6 = future.actionGet();
|
||||
assertThat(user5, sameInstance(user6));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.ldap;
|
||||
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
|
||||
|
@ -69,7 +70,9 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService));
|
||||
|
||||
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), arrayContaining("HMS Victory"));
|
||||
}
|
||||
|
@ -85,7 +88,9 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService));
|
||||
|
||||
User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), arrayContaining("HMS Victory"));
|
||||
}
|
||||
|
@ -101,8 +106,12 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
|
||||
ldapFactory = spy(ldapFactory);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService));
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
ldap.authenticate( new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
|
||||
//verify one and only one session -> caching is working
|
||||
verify(ldapFactory, times(1)).session(anyString(), any(SecuredString.class));
|
||||
|
@ -120,15 +129,21 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
DnRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService);
|
||||
ldapFactory = spy(ldapFactory);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, roleMapper);
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
|
||||
//verify one and only one session -> caching is working
|
||||
verify(ldapFactory, times(1)).session(anyString(), any(SecuredString.class));
|
||||
|
||||
roleMapper.notifyRefresh();
|
||||
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
|
||||
//we need to session again
|
||||
verify(ldapFactory, times(2)).session(anyString(), any(SecuredString.class));
|
||||
|
@ -146,8 +161,12 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
|
||||
ldapFactory = spy(ldapFactory);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService));
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD)), future);
|
||||
future.actionGet();
|
||||
|
||||
//verify two and only two binds -> caching is disabled
|
||||
verify(ldapFactory, times(2)).session(anyString(), any(SecuredString.class));
|
||||
|
@ -219,7 +238,9 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null));
|
||||
|
||||
User user = ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", SecuredStringTests.build(PASSWORD)));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", SecuredStringTests.build(PASSWORD)), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), arrayContaining("avenger"));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.pki;
|
||||
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
@ -83,7 +84,9 @@ public class PkiRealmTests extends ESTestCase {
|
|||
PkiRealm realm = new PkiRealm(new RealmConfig("", Settings.EMPTY, globalSettings), roleMapper, sslService);
|
||||
when(roleMapper.resolveRoles(anyString(), anyList())).thenReturn(Collections.<String>emptySet());
|
||||
|
||||
User user = realm.authenticate(token);
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(token, future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.principal(), is("Elasticsearch Test Node"));
|
||||
assertThat(user.roles(), is(notNullValue()));
|
||||
|
@ -100,7 +103,9 @@ public class PkiRealmTests extends ESTestCase {
|
|||
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
|
||||
|
||||
X509AuthenticationToken token = realm.token(threadContext);
|
||||
User user = realm.authenticate(token);
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(token, future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.principal(), is("elasticsearch"));
|
||||
assertThat(user.roles(), is(notNullValue()));
|
||||
|
@ -121,7 +126,9 @@ public class PkiRealmTests extends ESTestCase {
|
|||
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
|
||||
|
||||
X509AuthenticationToken token = realm.token(threadContext);
|
||||
User user = realm.authenticate(token);
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(token, future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(notNullValue()));
|
||||
assertThat(user.principal(), is("Elasticsearch Test Node"));
|
||||
assertThat(user.roles(), is(notNullValue()));
|
||||
|
@ -143,7 +150,9 @@ public class PkiRealmTests extends ESTestCase {
|
|||
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
|
||||
|
||||
X509AuthenticationToken token = realm.token(threadContext);
|
||||
User user = realm.authenticate(token);
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(token, future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(nullValue()));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.support;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.Realm;
|
||||
|
@ -19,6 +21,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
@ -47,13 +50,13 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
RealmConfig config = new RealmConfig("test_realm", settings, globalSettings);
|
||||
CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return new User("username", new String[] { "r1", "r2", "r3" });
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
listener.onResponse(new User("username", new String[] { "r1", "r2", "r3" }));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
throw new UnsupportedOperationException("this method should not be called");
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onFailure(new UnsupportedOperationException("this method should not be called"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,14 +71,27 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
public void testAuthCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
SecuredString pass = SecuredStringTests.build("pass");
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass), future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(3));
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass));
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass));
|
||||
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("a", pass), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("b", pass), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("c", pass), future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(3));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
|
@ -83,14 +99,26 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
|
||||
public void testLookupCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
realm.lookupUser("a");
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("a", future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("b", future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("c", future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(3));
|
||||
realm.lookupUser("a");
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("a", future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("b", future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("c", future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(0));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(3));
|
||||
|
@ -99,25 +127,33 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
public void testLookupAndAuthCache() {
|
||||
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
|
||||
// lookup first
|
||||
User lookedUp = realm.lookupUser("a");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("a", future);
|
||||
User lookedUp = future.actionGet();
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(0));
|
||||
assertThat(lookedUp.roles(), arrayContaining("lookupRole1", "lookupRole2"));
|
||||
|
||||
// now authenticate
|
||||
User user = realm.authenticate(new UsernamePasswordToken("a", SecuredStringTests.build("pass")));
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("a", SecuredStringTests.build("pass")), future);
|
||||
User user = future.actionGet();
|
||||
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
|
||||
user = realm.authenticate(new UsernamePasswordToken("b", SecuredStringTests.build("pass")));
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("b", SecuredStringTests.build("pass")), future);
|
||||
user = future.actionGet();
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
assertThat(user.roles(), arrayContaining("testRole1", "testRole2"));
|
||||
//now lookup b
|
||||
lookedUp = realm.lookupUser("b");
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("b", future);
|
||||
lookedUp = future.actionGet();
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
assertThat(user, sameInstance(lookedUp));
|
||||
|
@ -130,47 +166,69 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
SecuredString pass1 = SecuredStringTests.build("pass");
|
||||
SecuredString pass2 = SecuredStringTests.build("password");
|
||||
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass1));
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass1));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass1), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass1), future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(1));
|
||||
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2));
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2));
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2), future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken(user, pass2), future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.authInvocationCounter.intValue(), is(2));
|
||||
}
|
||||
|
||||
public void testAuthenticateContract() throws Exception {
|
||||
Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
User user = realm.authenticate(new UsernamePasswordToken("user", SecuredStringTests.build("pass")));
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user", SecuredStringTests.build("pass")), future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user , nullValue());
|
||||
|
||||
realm = new ThrowingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
user = realm.authenticate(new UsernamePasswordToken("user", SecuredStringTests.build("pass")));
|
||||
assertThat(user , nullValue());
|
||||
future = new PlainActionFuture<>();
|
||||
realm.authenticate(new UsernamePasswordToken("user", SecuredStringTests.build("pass")), future);
|
||||
RuntimeException e = expectThrows(RuntimeException.class, future::actionGet);
|
||||
assertThat(e.getMessage() , containsString("whatever exception"));
|
||||
}
|
||||
|
||||
public void testLookupContract() throws Exception {
|
||||
Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
User user = realm.lookupUser("user");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user", future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user , nullValue());
|
||||
|
||||
realm = new ThrowingAuthenticationRealm(Settings.EMPTY, globalSettings);
|
||||
user = realm.lookupUser("user");
|
||||
assertThat(user , nullValue());
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("user", future);
|
||||
RuntimeException e = expectThrows(RuntimeException.class, future::actionGet);
|
||||
assertThat(e.getMessage() , containsString("lookup exception"));
|
||||
}
|
||||
|
||||
public void testThatLookupIsNotCalledIfNotSupported() throws Exception {
|
||||
LookupNotSupportedRealm realm = new LookupNotSupportedRealm(globalSettings);
|
||||
assertThat(realm.userLookupSupported(), is(false));
|
||||
User user = realm.lookupUser("a");
|
||||
PlainActionFuture<User> future = new PlainActionFuture<>();
|
||||
realm.lookupUser("a", future);
|
||||
User user = future.actionGet();
|
||||
assertThat(user, is(nullValue()));
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
|
||||
// try to lookup more
|
||||
realm.lookupUser("b");
|
||||
realm.lookupUser("c");
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("b", future);
|
||||
future.actionGet();
|
||||
future = new PlainActionFuture<>();
|
||||
realm.lookupUser("c", future);
|
||||
future.actionGet();
|
||||
|
||||
assertThat(realm.lookupInvocationCounter.intValue(), is(0));
|
||||
}
|
||||
|
@ -184,17 +242,18 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings);
|
||||
final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
// do something slow
|
||||
if (BCrypt.checkpw(token.credentials(), passwordHash)) {
|
||||
return new User(username, new String[]{"r1", "r2", "r3"});
|
||||
listener.onResponse(new User(username, new String[]{"r1", "r2", "r3"}));
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
throw new UnsupportedOperationException("this method should not be called");
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onFailure(new UnsupportedOperationException("this method should not be called"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -217,12 +276,17 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
latch.await();
|
||||
for (int i = 0; i < numberOfIterations; i++) {
|
||||
UsernamePasswordToken token = new UsernamePasswordToken(username, invalidPassword ? randomPassword : password);
|
||||
User user = realm.authenticate(token);
|
||||
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((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!");
|
||||
}
|
||||
}, (e) -> {
|
||||
logger.error("caught exception", e);
|
||||
fail("unexpected exception");
|
||||
}));
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {}
|
||||
|
@ -245,13 +309,13 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings);
|
||||
final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
throw new UnsupportedOperationException("authenticate should not be called!");
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
listener.onFailure(new UnsupportedOperationException("authenticate should not be called!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
return new User(username, new String[]{"r1", "r2", "r3"});
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onResponse(new User(username, new String[]{"r1", "r2", "r3"}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -272,10 +336,14 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
try {
|
||||
latch.await();
|
||||
for (int i = 0; i < numberOfIterations; i++) {
|
||||
User user = realm.lookupUser(username);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("failed to lookup user");
|
||||
}
|
||||
realm.lookupUser(username, ActionListener.wrap((user) -> {
|
||||
if (user == null) {
|
||||
throw new RuntimeException("failed to lookup user");
|
||||
}
|
||||
}, (e) -> {
|
||||
logger.error("caught exception", e);
|
||||
fail("unexpected exception");
|
||||
}));
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {}
|
||||
|
@ -298,20 +366,20 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
super("failing", new RealmConfig("failing-test", settings, global));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLookupSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}
|
||||
|
||||
static class ThrowingAuthenticationRealm extends CachingUsernamePasswordRealm {
|
||||
|
@ -321,13 +389,13 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
throw new RuntimeException("whatever exception");
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
listener.onFailure(new RuntimeException("whatever exception"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
throw new RuntimeException("lookup exception");
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
listener.onFailure(new RuntimeException("lookup exception"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -346,15 +414,15 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
authInvocationCounter.incrementAndGet();
|
||||
return new User(token.principal(), new String[] { "testRole1", "testRole2" });
|
||||
listener.onResponse(new User(token.principal(), new String[] { "testRole1", "testRole2" }));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
lookupInvocationCounter.incrementAndGet();
|
||||
return new User(username, new String[] { "lookupRole1", "lookupRole2" });
|
||||
listener.onResponse(new User(username, new String[] { "lookupRole1", "lookupRole2" }));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -373,15 +441,15 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected User doAuthenticate(UsernamePasswordToken token) {
|
||||
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener) {
|
||||
authInvocationCounter.incrementAndGet();
|
||||
return new User(token.principal(), new String[] { "testRole1", "testRole2" });
|
||||
listener.onResponse(new User(token.principal(), new String[] { "testRole1", "testRole2" }));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User doLookupUser(String username) {
|
||||
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||
lookupInvocationCounter.incrementAndGet();
|
||||
throw new UnsupportedOperationException("don't call lookup if lookup isn't supported!!!");
|
||||
listener.onFailure(new UnsupportedOperationException("don't call lookup if lookup isn't supported!!!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue