shield: do not throw exception if authorization header is not a basic token
Custom realms may enable the use of other authorization schemes than just basic authentication and these schemes should work in addition to our built in realms. However, our built in realms use the UsernamePasswordToken class to parse the Authorization header, which had a check to ensure the token was for basic authentication and if not, an exception was thrown. The throwing of the exception stops the authentication process and prevents custom realms from evaluating the header if they come later in the ordering of realms. This change removes the throwing of the exception unless the header starts with 'Basic ' and is invalid. Original commit: elastic/x-pack-elasticsearch@fd438ded95
This commit is contained in:
parent
2fdfec0ae8
commit
95a8f77146
|
@ -22,7 +22,7 @@ public abstract class UsernamePasswordRealm extends Realm<UsernamePasswordToken>
|
|||
|
||||
@Override
|
||||
public UsernamePasswordToken token(ThreadContext threadContext) {
|
||||
return UsernamePasswordToken.extractToken(threadContext, null);
|
||||
return UsernamePasswordToken.extractToken(threadContext);
|
||||
}
|
||||
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
|
|
|
@ -7,15 +7,12 @@ package org.elasticsearch.shield.authc.support;
|
|||
|
||||
import org.elasticsearch.common.Base64;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.shield.authc.AuthenticationToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.elasticsearch.shield.support.Exceptions.authenticationError;
|
||||
|
||||
|
@ -25,7 +22,7 @@ import static org.elasticsearch.shield.support.Exceptions.authenticationError;
|
|||
public class UsernamePasswordToken implements AuthenticationToken {
|
||||
|
||||
public static final String BASIC_AUTH_HEADER = "Authorization";
|
||||
private static final Pattern BASIC_AUTH_PATTERN = Pattern.compile("Basic\\s(.+)");
|
||||
private static final String BASIC_AUTH_PREFIX = "Basic ";
|
||||
|
||||
private final String username;
|
||||
private final SecuredString password;
|
||||
|
@ -66,34 +63,30 @@ public class UsernamePasswordToken implements AuthenticationToken {
|
|||
return Objects.hash(username, password.hashCode());
|
||||
}
|
||||
|
||||
public static UsernamePasswordToken extractToken(ThreadContext context, UsernamePasswordToken defaultToken) {
|
||||
public static UsernamePasswordToken extractToken(ThreadContext context) {
|
||||
String authStr = context.getHeader(BASIC_AUTH_HEADER);
|
||||
if (authStr == null) {
|
||||
return defaultToken;
|
||||
return null;
|
||||
}
|
||||
|
||||
return extractToken(authStr);
|
||||
}
|
||||
|
||||
public static UsernamePasswordToken extractToken(RestRequest request, UsernamePasswordToken defaultToken) {
|
||||
String authStr = request.header(BASIC_AUTH_HEADER);
|
||||
if (authStr == null) {
|
||||
return defaultToken;
|
||||
private static UsernamePasswordToken extractToken(String headerValue) {
|
||||
if (headerValue.startsWith(BASIC_AUTH_PREFIX) == false) {
|
||||
// the header does not start with 'Basic ' so we cannot use it, but it may be valid for another realm
|
||||
return null;
|
||||
}
|
||||
|
||||
return extractToken(authStr);
|
||||
}
|
||||
|
||||
static UsernamePasswordToken extractToken(String token) {
|
||||
Matcher matcher = BASIC_AUTH_PATTERN.matcher(token.trim());
|
||||
if (!matcher.matches()) {
|
||||
// if there is nothing after the prefix, the header is bad
|
||||
if (headerValue.length() == BASIC_AUTH_PREFIX.length()) {
|
||||
throw authenticationError("invalid basic authentication header value");
|
||||
}
|
||||
|
||||
char[] userpasswd;
|
||||
try {
|
||||
userpasswd = CharArrays.utf8BytesToChars(Base64.decode(matcher.group(1)));
|
||||
} catch (IllegalArgumentException|IOException e) {
|
||||
userpasswd = CharArrays.utf8BytesToChars(Base64.decode(headerValue.substring(BASIC_AUTH_PREFIX.length()).trim()));
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
throw authenticationError("invalid basic authentication header encoding", e);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.elasticsearch.ElasticsearchSecurityException;
|
|||
import org.elasticsearch.common.Base64;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
@ -21,8 +20,6 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -51,19 +48,19 @@ public class UsernamePasswordTokenTests extends ESTestCase {
|
|||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
String header = "Basic " + Base64.encodeBytes("user1:test123".getBytes(StandardCharsets.UTF_8));
|
||||
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, header);
|
||||
UsernamePasswordToken token = UsernamePasswordToken.extractToken(threadContext, null);
|
||||
UsernamePasswordToken token = UsernamePasswordToken.extractToken(threadContext);
|
||||
assertThat(token, notNullValue());
|
||||
assertThat(token.principal(), equalTo("user1"));
|
||||
assertThat(new String(token.credentials().internalChars()), equalTo("test123"));
|
||||
}
|
||||
|
||||
public void testExtractTokenInvalid() throws Exception {
|
||||
String[] invalidValues = { "Basic", "Basic ", "Basic f" };
|
||||
String[] invalidValues = { "Basic ", "Basic f" };
|
||||
for (String value : invalidValues) {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, value);
|
||||
try {
|
||||
UsernamePasswordToken.extractToken(threadContext, null);
|
||||
UsernamePasswordToken.extractToken(threadContext);
|
||||
fail("Expected an authentication exception for invalid basic auth token [" + value + "]");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
// expected
|
||||
|
@ -72,44 +69,17 @@ public class UsernamePasswordTokenTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testThatAuthenticationExceptionContainsResponseHeaders() {
|
||||
public void testHeaderNotMatchingReturnsNull() {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
String header = "BasicBroken";
|
||||
String header = randomFrom("BasicBroken", "invalid", "Basic");
|
||||
threadContext.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, header);
|
||||
try {
|
||||
UsernamePasswordToken.extractToken(threadContext, null);
|
||||
fail("Expected exception but did not happen");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
UsernamePasswordToken extracted = UsernamePasswordToken.extractToken(threadContext);
|
||||
assertThat(extracted, nullValue());
|
||||
}
|
||||
|
||||
public void testExtractTokenRest() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
UsernamePasswordToken token = new UsernamePasswordToken("username", SecuredStringTests.build("changeme"));
|
||||
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(UsernamePasswordToken.basicAuthHeaderValue("username",
|
||||
SecuredStringTests.build("changeme")));
|
||||
assertThat(UsernamePasswordToken.extractToken(request, null), equalTo(token));
|
||||
}
|
||||
|
||||
public void testExtractTokenRestMissing() throws Exception {
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn(null);
|
||||
assertThat(UsernamePasswordToken.extractToken(request, null), nullValue());
|
||||
}
|
||||
|
||||
public void testExtractTokenRestWithInvalidToken1() throws Exception {
|
||||
thrown.expect(ElasticsearchSecurityException.class);
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("invalid");
|
||||
UsernamePasswordToken.extractToken(request, null);
|
||||
}
|
||||
|
||||
public void testExtractTokenRestWithInvalidToken2() throws Exception {
|
||||
thrown.expect(ElasticsearchSecurityException.class);
|
||||
RestRequest request = mock(RestRequest.class);
|
||||
when(request.header(UsernamePasswordToken.BASIC_AUTH_HEADER)).thenReturn("Basic");
|
||||
UsernamePasswordToken.extractToken(request, null);
|
||||
public void testExtractTokenMissing() throws Exception {
|
||||
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
|
||||
assertThat(UsernamePasswordToken.extractToken(threadContext), nullValue());
|
||||
}
|
||||
|
||||
public void testEqualsWithDifferentPasswords() {
|
||||
|
|
Loading…
Reference in New Issue