Have AuthenticationService resolve the auth token from rest request

- Also made sure that we fallback on system token only if the system has permission to the action.
- While at it, change the binding of the different services to run as a singletons

Closes elastic/elasticsearch#64

Original commit: elastic/x-pack-elasticsearch@3705b7365a
This commit is contained in:
uboness 2014-09-06 13:51:03 +02:00
parent 5cc210bc9a
commit 263ebfbbf2
24 changed files with 254 additions and 124 deletions

View File

@ -16,9 +16,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.*;
import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.authc.system.SystemRealm;
import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.authz.SystemRole;
import org.elasticsearch.shield.transport.TransportFilter;
import org.elasticsearch.transport.TransportRequest;
@ -31,29 +31,31 @@ public class SecurityFilter extends AbstractComponent {
private final AuthorizationService authzService;
@Inject
public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, RestController restController) {
public SecurityFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService) {
super(settings);
this.authcService = authcService;
this.authzService = authzService;
restController.registerFilter(new Rest(this));
}
void process(String action, TransportRequest request) {
AuthenticationToken token = authcService.token(action, request, SystemRealm.TOKEN);
// if the action is a system action, we'll fall back on the system user, otherwise we
// won't fallback on any user and an authentication exception will be thrown
AuthenticationToken defaultToken = SystemRole.INSTANCE.check(action) ? SystemRealm.TOKEN : null;
AuthenticationToken token = authcService.token(action, request, defaultToken);
User user = authcService.authenticate(action, request, token);
authzService.authorize(user, action, request);
}
public static class Rest extends RestFilter {
static {
BaseRestHandler.addUsefulHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
private final SecurityFilter filter;
public Rest(SecurityFilter filter) {
@Inject
public Rest(SecurityFilter filter, RestController controller) {
this.filter = filter;
controller.registerFilter(this);
}
@Override
@ -63,7 +65,7 @@ public class SecurityFilter extends AbstractComponent {
@Override
public void process(RestRequest request, RestChannel channel, RestFilterChain filterChain) throws Exception {
filter.authcService.verifyToken(request);
filter.authcService.extractAndRegisterToken(request);
filterChain.continueProcessing(request, channel);
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.shield;
import org.elasticsearch.common.inject.AbstractModule;
/**
*
*/
public class SecurityFilterModule extends AbstractModule {
@Override
protected void configure() {
bind(SecurityFilter.class).asEagerSingleton();
}
}

View File

@ -15,6 +15,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.audit.AuditTrailModule;
import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.shield.authz.AuthorizationModule;
import org.elasticsearch.shield.transport.SecuredRestModule;
import org.elasticsearch.shield.transport.SecuredTransportModule;
/**
@ -48,14 +49,16 @@ public class SecurityModule extends AbstractModule implements SpawnModules, PreP
// spawn needed parts in client mode
if (isClient) {
return ImmutableList.of(new SecuredTransportModule());
return ImmutableList.of(new SecuredTransportModule(), new SecurityFilterModule());
}
return ImmutableList.of(
new AuthenticationModule(settings),
new AuthorizationModule(),
new AuditTrailModule(settings),
new SecuredTransportModule());
new SecuredTransportModule(),
new SecuredRestModule(),
new SecurityFilterModule());
}
@Override

View File

@ -36,7 +36,7 @@ public class AuditTrailModule extends AbstractModule {
bind(AuditTrail.class).toInstance(AuditTrail.NOOP);
return;
}
bind(AuditTrail.class).to(AuditTrailService.class);
bind(AuditTrail.class).to(AuditTrailService.class).asEagerSingleton();
Multibinder<AuditTrail> binder = Multibinder.newSetBinder(binder(), AuditTrail.class);
Set<String> uniqueOutputs = Sets.newHashSet(outputs);

View File

@ -49,7 +49,7 @@ public class AuthenticationModule extends AbstractModule implements SpawnModules
@Override
protected void configure() {
bind(AuthenticationService.class).to(InternalAuthenticationService.class);
bind(AuthenticationService.class).to(InternalAuthenticationService.class).asEagerSingleton();
if (!esusersEnabled) {
bind(ESUsersRealm.class).toProvider(Providers.of((ESUsersRealm) null));
}

View File

@ -15,10 +15,10 @@ import org.elasticsearch.transport.TransportMessage;
public interface AuthenticationService {
/**
* Inspects the given rest request and verifies it carries an authentication token, if it doesn't
* an {@link AuthenticationException} is thrown
* Extracts an authentication token from the given rest request and if found registers it on
* the request. If not found, an {@link AuthenticationException} is thrown.
*/
void verifyToken(RestRequest request) throws AuthenticationException;
void extractAndRegisterToken(RestRequest request) throws AuthenticationException;
/**
* Extracts the authenticate token from the given message. If no recognized auth token is associated

View File

@ -35,9 +35,11 @@ public class InternalAuthenticationService extends AbstractComponent implements
}
@Override
public void verifyToken(RestRequest request) throws AuthenticationException {
public void extractAndRegisterToken(RestRequest request) throws AuthenticationException {
for (Realm realm : realms) {
if (realm.hasToken(request)) {
AuthenticationToken token = realm.token(request);
if (token != null) {
request.putInContext(TOKEN_CTX_KEY, token);
return;
}
}

View File

@ -21,24 +21,30 @@ public interface Realm<T extends AuthenticationToken> {
*/
String type();
boolean hasToken(RestRequest request);
/**
* Attempts to extract a authentication token from the request. If an appropriate token is found
* {@link #authenticate(AuthenticationToken)} will be called for an authentication attempt. If no
* appropriate token is found, {@code null} is returned.
*
* @param message The request
* @return The authentication token this realm can authenticate, {@code null} if no such
* token is found
*/
T token(TransportMessage<?> message);
/**
* @return {@code true} if this realm supports the given authentication token, {@code false} otherwise.
*/
boolean supports(AuthenticationToken token);
/**
* Attempts to extract an authentication token from the given rest request. If an appropriate token
* is found it's returned, otherwise {@code null} is returned.
*
* @param request The rest request
* @return The authentication token or {@code null} if not found
*/
T token(RestRequest request);
/**
* Attempts to extract an authentication token from the given transport message. If an appropriate token
* is found it's returned, otherwise {@code null} is returned.
*
* @param message The transport message
* @return The authentication token or {@code null} if not found
*/
T token(TransportMessage<?> message);
/**
* Authenticates the given token. A successful authentication will return the User associated
* with the given token. An unsuccessful authentication returns {@code null}.

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.name.Named;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
@ -24,6 +25,10 @@ import org.elasticsearch.transport.TransportMessage;
*/
public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePasswordToken> {
static {
BaseRestHandler.addUsefulHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
public static final String TYPE = "esusers";
final UserPasswdStore userPasswdStore;
@ -42,8 +47,8 @@ public class ESUsersRealm extends AbstractComponent implements Realm<UsernamePas
}
@Override
public boolean hasToken(RestRequest request) {
return UsernamePasswordToken.hasToken(request);
public UsernamePasswordToken token(RestRequest request) {
return UsernamePasswordToken.extractToken(request, null);
}
@Override

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.SecurityException;
@ -24,6 +25,10 @@ import java.util.Set;
*/
public class LdapRealm extends CachingUsernamePasswordRealm implements Realm<UsernamePasswordToken> {
static {
BaseRestHandler.addUsefulHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER);
}
public static final String TYPE = "ldap";
private final LdapConnectionFactory connectionFactory;

View File

@ -44,8 +44,8 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp
}
@Override
public boolean hasToken(RestRequest request) {
return UsernamePasswordToken.hasToken(request);
public UsernamePasswordToken token(RestRequest request) {
return UsernamePasswordToken.extractToken(request, null);
}
@Override

View File

@ -13,6 +13,8 @@ import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -22,7 +24,6 @@ import java.util.regex.Pattern;
public class UsernamePasswordToken implements AuthenticationToken {
public static final String BASIC_AUTH_HEADER = "Authorization";
private static final String TOKEN_KEY = "X-ES-UsernamePasswordToken";
private static final Pattern BASIC_AUTH_PATTERN = Pattern.compile("Basic\\s(.+)");
private final String username;
@ -43,9 +44,20 @@ public class UsernamePasswordToken implements AuthenticationToken {
return password;
}
public static boolean hasToken(RestRequest request) {
String header = request.header(BASIC_AUTH_HEADER);
return header != null && BASIC_AUTH_PATTERN.matcher(header).matches();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UsernamePasswordToken that = (UsernamePasswordToken) o;
return Arrays.equals(password, that.password) &&
Objects.equals(username, that.username);
}
@Override
public int hashCode() {
return Objects.hash(username, Arrays.hashCode(password));
}
public static UsernamePasswordToken extractToken(TransportMessage<?> message, UsernamePasswordToken defaultToken) {
@ -67,6 +79,25 @@ public class UsernamePasswordToken implements AuthenticationToken {
return new UsernamePasswordToken(userpasswd.substring(0, i), userpasswd.substring(i+1).toCharArray());
}
public static UsernamePasswordToken extractToken(RestRequest request, UsernamePasswordToken defaultToken) {
String authStr = request.header(BASIC_AUTH_HEADER);
if (authStr == null) {
return defaultToken;
}
Matcher matcher = BASIC_AUTH_PATTERN.matcher(authStr.trim());
if (!matcher.matches()) {
throw new AuthenticationException("Invalid basic authentication header value");
}
String userpasswd = new String(Base64.decodeBase64(matcher.group(1)), Charsets.UTF_8);
int i = userpasswd.indexOf(':');
if (i < 0) {
throw new AuthenticationException("Invalid basic authentication header value");
}
return new UsernamePasswordToken(userpasswd.substring(0, i), userpasswd.substring(i+1).toCharArray());
}
public static void putTokenHeader(TransportRequest request, UsernamePasswordToken token) {
request.putHeader("Authorization", basicAuthHeaderValue(token.username, token.password));
}

View File

@ -35,8 +35,8 @@ public class SystemRealm implements Realm<AuthenticationToken> {
}
@Override
public boolean hasToken(RestRequest request) {
return false; // system calls never come from rest interface
public AuthenticationToken token(RestRequest request) {
return null; // system token can never come from the rest API
}
@Override

View File

@ -17,6 +17,6 @@ public class AuthorizationModule extends AbstractModule {
@Override
protected void configure() {
bind(RolesStore.class).to(FileRolesStore.class);
bind(AuthorizationService.class).to(InternalAuthorizationService.class);
bind(AuthorizationService.class).to(InternalAuthorizationService.class).asEagerSingleton();
}
}

View File

@ -26,6 +26,8 @@ import org.elasticsearch.shield.support.Automatons;
import java.util.Locale;
import java.util.Set;
import static org.elasticsearch.shield.support.Automatons.patterns;
/**
*
*/
@ -33,7 +35,7 @@ public abstract class Privilege<P extends Privilege<P>> {
static final String SUB_ACTION_SUFFIX_PATTERN = ".*";
public static final Internal INTERNAL = new Internal();
public static final System SYSTEM = new System();
protected final Name name;
@ -54,11 +56,13 @@ public abstract class Privilege<P extends Privilege<P>> {
return this.implies(other) && other.implies((P) this);
}
public static class Internal extends Privilege<Internal> {
public static class System extends Privilege<System> {
protected static final Predicate<String> PREDICATE = new AutomatonPredicate(Automatons.patterns("internal:.*"));
protected static final Predicate<String> PREDICATE = new AutomatonPredicate(patterns(
"internal:.*"
));
private Internal() {
private System() {
super(new Name("internal"));
}
@ -68,7 +72,7 @@ public abstract class Privilege<P extends Privilege<P>> {
}
@Override
public boolean implies(Internal other) {
public boolean implies(System other) {
return true;
}
}
@ -261,12 +265,12 @@ public abstract class Privilege<P extends Privilege<P>> {
private AutomatonPrivilege(String name, String... patterns) {
super(new Name(name));
this.automaton = Automatons.patterns(patterns);
this.automaton = patterns(patterns);
}
private AutomatonPrivilege(Name name, String... patterns) {
super(name);
this.automaton = Automatons.patterns(patterns);
this.automaton = patterns(patterns);
}
private AutomatonPrivilege(Name name, Automaton automaton) {

View File

@ -17,11 +17,15 @@ public class SystemRole extends Permission.Global {
public static final SystemRole INSTANCE = new SystemRole();
public static final String NAME = "__es_system_role";
private static final Predicate<String> PREDICATE = Privilege.INTERNAL.predicate();
private static final Predicate<String> PREDICATE = Privilege.SYSTEM.predicate();
private SystemRole() {
}
public boolean check(String action) {
return PREDICATE.apply(action);
}
@Override
public boolean check(String action, TransportRequest request, MetaData metaData) {
return PREDICATE.apply(action);

View File

@ -0,0 +1,20 @@
/*
* 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.shield.transport;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.shield.SecurityFilter;
/**
*
*/
public class SecuredRestModule extends AbstractModule {
@Override
protected void configure() {
bind(SecurityFilter.Rest.class).asEagerSingleton();
}
}

View File

@ -5,21 +5,17 @@
*/
package org.elasticsearch.shield;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.*;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestFilterChain;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.authc.AuthenticationException;
import org.elasticsearch.shield.authc.AuthenticationService;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.authc.system.SystemRealm;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.authz.AuthorizationService;
@ -30,7 +26,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.*;
@ -52,7 +47,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
authcService = mock(AuthenticationService.class);
authzService = mock(AuthorizationService.class);
restController = mock(RestController.class);
filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService, restController);
filter = new SecurityFilter(ImmutableSettings.EMPTY, authcService, authzService);
}
@Test
@ -60,19 +55,30 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
TransportRequest request = new InternalRequest();
AuthenticationToken token = mock(AuthenticationToken.class);
User user = new User.Simple("_username", "r1");
when(authcService.token("_action", request, SystemRealm.TOKEN)).thenReturn(token);
when(authcService.token("_action", request, null)).thenReturn(token);
when(authcService.authenticate("_action", request, token)).thenReturn(user);
filter.process("_action", request);
verify(authzService).authorize(user, "_action", request);
}
@Test
public void testProcess_InternalAction() throws Exception {
TransportRequest request = new InternalRequest();
AuthenticationToken token = mock(AuthenticationToken.class);
User user = new User.Simple("_username", "r1");
when(authcService.token("internal:_action", request, SystemRealm.TOKEN)).thenReturn(token);
when(authcService.authenticate("internal:_action", request, token)).thenReturn(user);
filter.process("internal:_action", request);
verify(authzService).authorize(user, "internal:_action", request);
}
@Test
public void testProcess_AuthenticationFails_Authenticate() throws Exception {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("failed authc");
TransportRequest request = new InternalRequest();
AuthenticationToken token = mock(AuthenticationToken.class);
when(authcService.token("_action", request, SystemRealm.TOKEN)).thenReturn(token);
when(authcService.token("_action", request, null)).thenReturn(token);
when(authcService.authenticate("_action", request, token)).thenThrow(new AuthenticationException("failed authc"));
filter.process("_action", request);
}
@ -82,7 +88,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("failed authc");
TransportRequest request = new InternalRequest();
when(authcService.token("_action", request, SystemRealm.TOKEN)).thenThrow(new AuthenticationException("failed authc"));
when(authcService.token("_action", request, null)).thenThrow(new AuthenticationException("failed authc"));
filter.process("_action", request);
}
@ -93,7 +99,7 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
TransportRequest request = new InternalRequest();
AuthenticationToken token = mock(AuthenticationToken.class);
User user = new User.Simple("_username", "r1");
when(authcService.token("_action", request, SystemRealm.TOKEN)).thenReturn(token);
when(authcService.token("_action", request, null)).thenReturn(token);
when(authcService.authenticate("_action", request, token)).thenReturn(user);
doThrow(new AuthorizationException("failed authz")).when(authzService).authorize(user, "_action", request);
filter.process("_action", request);
@ -147,12 +153,13 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
@Test
public void testRest_WithToken() throws Exception {
SecurityFilter.Rest rest = new SecurityFilter.Rest(filter);
SecurityFilter.Rest rest = new SecurityFilter.Rest(filter, restController);
RestRequest request = mock(RestRequest.class);
RestChannel channel = mock(RestChannel.class);
RestFilterChain chain = mock(RestFilterChain.class);
rest.process(request, channel, chain);
verify(authcService).verifyToken(request);
verify(authcService).extractAndRegisterToken(request);
verify(restController).registerFilter(rest);
}
@Test
@ -160,42 +167,13 @@ public class SecurityFilterTests extends ElasticsearchTestCase {
AuthenticationException exception = new AuthenticationException("no token");
thrown.expect(AuthenticationException.class);
thrown.expectMessage("no token");
SecurityFilter.Rest rest = new SecurityFilter.Rest(filter);
SecurityFilter.Rest rest = new SecurityFilter.Rest(filter, restController);
RestRequest request = mock(RestRequest.class);
RestChannel channel = mock(RestChannel.class);
RestFilterChain chain = mock(RestFilterChain.class);
doThrow(exception).when(authcService).verifyToken(request);
doThrow(exception).when(authcService).extractAndRegisterToken(request);
rest.process(request, channel, chain);
}
@Test
public void testRestHeadersAreCopied() throws Exception {
SecurityFilter.Rest.class.getName(); // just to make sure Rest class is loaded
Client client = mock(Client.class);
AdminClient adminClient = mock(AdminClient.class);
when(client.admin()).thenReturn(adminClient);
when(adminClient.cluster()).thenReturn(mock(ClusterAdminClient.class));
when(adminClient.indices()).thenReturn(mock(IndicesAdminClient.class));
final ActionRequest request = new ActionRequest() {
@Override
public ActionRequestValidationException validate() {
return null;
}
};
final Action action = mock(Action.class);
final ActionListener listener = mock(ActionListener.class);
BaseRestHandler handler = new BaseRestHandler(ImmutableSettings.EMPTY, client) {
@Override
protected void handleRequest(RestRequest restRequest, RestChannel channel, Client client) throws Exception {
client.execute(action, request, listener);
}
};
RestRequest restRequest = mock(RestRequest.class);
when(restRequest.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("foobar");
RestChannel channel = mock(RestChannel.class);
handler.handleRequest(restRequest, channel);
assertThat((String) request.getHeader(UsernamePasswordToken.BASIC_AUTH_HEADER), equalTo("foobar"));
verify(restController).registerFilter(rest);
}
private static class InternalRequest extends TransportRequest {

View File

@ -7,7 +7,6 @@ package org.elasticsearch.shield.authc;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.test.ElasticsearchTestCase;
@ -162,19 +161,21 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
}
@Test
public void testVerifyToken_Exists() throws Exception {
when(firstRealm.hasToken(restRequest)).thenReturn(false);
when(secondRealm.hasToken(restRequest)).thenReturn(true);
service.verifyToken(restRequest);
public void testExtractAndRegisterToken_Exists() throws Exception {
AuthenticationToken token = mock(AuthenticationToken.class);
when(firstRealm.token(restRequest)).thenReturn(null);
when(secondRealm.token(restRequest)).thenReturn(token);
service.extractAndRegisterToken(restRequest);
assertThat(restRequest.getFromContext(InternalAuthenticationService.TOKEN_CTX_KEY), equalTo((Object) token));
}
@Test
public void testVerifyToken_Missing() throws Exception {
thrown.expect(AuthenticationException.class);
thrown.expectMessage("Missing authentication token");
when(firstRealm.hasToken(restRequest)).thenReturn(false);
when(secondRealm.hasToken(restRequest)).thenReturn(false);
service.verifyToken(restRequest);
when(firstRealm.token(restRequest)).thenReturn(null);
when(secondRealm.token(restRequest)).thenReturn(null);
service.extractAndRegisterToken(restRequest);
}
private static class InternalMessage extends TransportMessage<InternalMessage> {

View File

@ -5,19 +5,34 @@
*/
package org.elasticsearch.shield.authc.esusers;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.SecurityFilter;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.support.UserPasswdStore;
import org.elasticsearch.shield.authc.support.UserRolesStore;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.hamcrest.Matchers;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.arrayContaining;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
@ -97,4 +112,34 @@ public class ESUsersRealmTests extends ElasticsearchTestCase {
return roles;
}
}
@Test @SuppressWarnings("unchecked")
public void testRestHeadersAreCopied() throws Exception {
// the required header will be registered only if ESUsersRealm is actually used.
new ESUsersRealm(ImmutableSettings.EMPTY, null, null);
Client client = mock(Client.class);
AdminClient adminClient = mock(AdminClient.class);
when(client.admin()).thenReturn(adminClient);
when(adminClient.cluster()).thenReturn(mock(ClusterAdminClient.class));
when(adminClient.indices()).thenReturn(mock(IndicesAdminClient.class));
final ActionRequest request = new ActionRequest() {
@Override
public ActionRequestValidationException validate() {
return null;
}
};
final Action action = mock(Action.class);
final ActionListener listener = mock(ActionListener.class);
BaseRestHandler handler = new BaseRestHandler(ImmutableSettings.EMPTY, client) {
@Override
protected void handleRequest(RestRequest restRequest, RestChannel channel, Client client) throws Exception {
client.execute(action, request, listener);
}
};
RestRequest restRequest = mock(RestRequest.class);
when(restRequest.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("foobar");
RestChannel channel = mock(RestChannel.class);
handler.handleRequest(restRequest, channel);
assertThat((String) request.getHeader(UsernamePasswordToken.BASIC_AUTH_HEADER), Matchers.equalTo("foobar"));
}
}

View File

@ -10,8 +10,6 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authz.Permission;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;

View File

@ -6,13 +6,12 @@
package org.elasticsearch.shield.authc.support;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class CachingUsernamePasswordRealmTests {
@ -27,11 +26,6 @@ public class CachingUsernamePasswordRealmTests {
}
@Override public String type() { return "test"; }
@Override
public boolean hasToken(RestRequest request) {
return true;
}
}

View File

@ -11,7 +11,9 @@ import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.authc.AuthenticationException;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
import static org.hamcrest.Matchers.*;
@ -23,6 +25,9 @@ import static org.mockito.Mockito.when;
*/
public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testPutToken() throws Exception {
TransportRequest request = new TransportRequest() {};
@ -80,25 +85,33 @@ public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
}
@Test
public void testHasToken() throws Exception {
public void testExtractTokenRest() throws Exception {
RestRequest request = mock(RestRequest.class);
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("Basic foobar");
assertThat(UsernamePasswordToken.hasToken(request), is(true));
UsernamePasswordToken token = new UsernamePasswordToken("username", "changeme".toCharArray());
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(UsernamePasswordToken.basicAuthHeaderValue("username", "changeme".toCharArray()));
assertThat(UsernamePasswordToken.extractToken(request, null), equalTo(token));
}
@Test
public void testHasToken_Missing() throws Exception {
public void testExtractTokenRest_Missing() throws Exception {
RestRequest request = mock(RestRequest.class);
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(null);
assertThat(UsernamePasswordToken.hasToken(request), is(false));
assertThat(UsernamePasswordToken.extractToken(request, null), nullValue());
}
@Test
public void testHasToken_WithInvalidToken() throws Exception {
public void testExtractTokenRest_WithInvalidToken1() throws Exception {
thrown.expect(AuthenticationException.class);
RestRequest request = mock(RestRequest.class);
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("invalid");
assertThat(UsernamePasswordToken.hasToken(request), is(false));
UsernamePasswordToken.extractToken(request, null);
}
@Test
public void testExtractTokenRest_WithInvalidToken2() throws Exception {
thrown.expect(AuthenticationException.class);
RestRequest request = mock(RestRequest.class);
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("Basic");
assertThat(UsernamePasswordToken.hasToken(request), is(false));
UsernamePasswordToken.extractToken(request, null);
}
}

View File

@ -38,7 +38,7 @@ import static org.hamcrest.Matchers.is;
@Ignore
@AbstractRandomizedTest.Integration
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0)
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0, maxNumDataNodes = 1)
public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest {
protected static final String DEFAULT_USER_NAME = "test_user";