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:
parent
5cc210bc9a
commit
263ebfbbf2
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue