diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java index d6f678a2dcb90..736b9378e3876 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java @@ -12,9 +12,11 @@ import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.xpack.core.XPackField; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.elasticsearch.xpack.core.security.support.Exceptions.authenticationError; @@ -44,12 +46,42 @@ public DefaultAuthenticationFailureHandler() { * be sent as failure response. * @see Realm#getAuthenticationFailureHeaders() */ - public DefaultAuthenticationFailureHandler(Map> failureResponseHeaders) { + public DefaultAuthenticationFailureHandler(final Map> failureResponseHeaders) { if (failureResponseHeaders == null || failureResponseHeaders.isEmpty()) { - failureResponseHeaders = Collections.singletonMap("WWW-Authenticate", + this.defaultFailureResponseHeaders = Collections.singletonMap("WWW-Authenticate", Collections.singletonList("Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\"")); + } else { + this.defaultFailureResponseHeaders = Collections.unmodifiableMap(failureResponseHeaders.entrySet().stream().collect(Collectors + .toMap(entry -> entry.getKey(), entry -> { + if (entry.getKey().equalsIgnoreCase("WWW-Authenticate")) { + List values = new ArrayList<>(entry.getValue()); + Collections.sort(values, (o1, o2) -> authSchemePriority(o1).compareTo(authSchemePriority(o2))); + return Collections.unmodifiableList(values); + } else { + return Collections.unmodifiableList(entry.getValue()); + } + }))); + } + } + + /** + * For given 'WWW-Authenticate' header value returns the priority based on + * the auth-scheme. Lower number denotes more secure and preferred + * auth-scheme than the higher number. + * + * @param headerValue string starting with auth-scheme name + * @return integer value denoting priority for given auth scheme. + */ + private static Integer authSchemePriority(final String headerValue) { + if (headerValue.regionMatches(true, 0, "negotiate", 0, "negotiate".length())) { + return 0; + } else if (headerValue.regionMatches(true, 0, "bearer", 0, "bearer".length())) { + return 1; + } else if (headerValue.regionMatches(true, 0, "basic", 0, "basic".length())) { + return 2; + } else { + return 3; } - this.defaultFailureResponseHeaders = Collections.unmodifiableMap(failureResponseHeaders); } @Override diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandlerTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandlerTests.java index 2598461c37280..15593f0b82ea5 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandlerTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandlerTests.java @@ -50,7 +50,7 @@ public void testAuthenticationRequired() { if (testDefault) { assertWWWAuthenticateWithSchemes(ese, basicAuthScheme); } else { - assertWWWAuthenticateWithSchemes(ese, basicAuthScheme, bearerAuthScheme); + assertWWWAuthenticateWithSchemes(ese, bearerAuthScheme, basicAuthScheme); } } @@ -83,12 +83,12 @@ public void testExceptionProcessingRequest() { assertThat(ese.getHeader("WWW-Authenticate"), is(notNullValue())); assertThat(ese, is(sameInstance(cause))); if (withAuthenticateHeader == false) { - assertWWWAuthenticateWithSchemes(ese, basicAuthScheme, bearerAuthScheme, negotiateAuthScheme); + assertWWWAuthenticateWithSchemes(ese, negotiateAuthScheme, bearerAuthScheme, basicAuthScheme); } else { if (selectedScheme.contains("Negotiate ")) { assertWWWAuthenticateWithSchemes(ese, selectedScheme); } else { - assertWWWAuthenticateWithSchemes(ese, basicAuthScheme, bearerAuthScheme, negotiateAuthScheme); + assertWWWAuthenticateWithSchemes(ese, negotiateAuthScheme, bearerAuthScheme, basicAuthScheme); } } assertThat(ese.getMessage(), equalTo("unauthorized")); @@ -102,11 +102,30 @@ public void testExceptionProcessingRequest() { assertThat(ese, is(notNullValue())); assertThat(ese.getHeader("WWW-Authenticate"), is(notNullValue())); assertThat(ese.getMessage(), equalTo("error attempting to authenticate request")); - assertWWWAuthenticateWithSchemes(ese, basicAuthScheme, bearerAuthScheme, negotiateAuthScheme); + assertWWWAuthenticateWithSchemes(ese, negotiateAuthScheme, bearerAuthScheme, basicAuthScheme); } } + public void testSortsWWWAuthenticateHeaderValues() { + final String basicAuthScheme = "Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\""; + final String bearerAuthScheme = "Bearer realm=\"" + XPackField.SECURITY + "\""; + final String negotiateAuthScheme = randomFrom("Negotiate", "Negotiate Ijoijksdk"); + final Map> failureResponeHeaders = new HashMap<>(); + final List supportedSchemes = Arrays.asList(basicAuthScheme, bearerAuthScheme, negotiateAuthScheme); + Collections.shuffle(supportedSchemes, random()); + failureResponeHeaders.put("WWW-Authenticate", supportedSchemes); + final DefaultAuthenticationFailureHandler failuerHandler = new DefaultAuthenticationFailureHandler(failureResponeHeaders); + + final ElasticsearchSecurityException ese = failuerHandler.exceptionProcessingRequest(Mockito.mock(RestRequest.class), null, + new ThreadContext(Settings.builder().build())); + + assertThat(ese, is(notNullValue())); + assertThat(ese.getHeader("WWW-Authenticate"), is(notNullValue())); + assertThat(ese.getMessage(), equalTo("error attempting to authenticate request")); + assertWWWAuthenticateWithSchemes(ese, negotiateAuthScheme, bearerAuthScheme, basicAuthScheme); + } + private void assertWWWAuthenticateWithSchemes(final ElasticsearchSecurityException ese, final String... schemes) { assertThat(ese.getHeader("WWW-Authenticate").size(), is(schemes.length)); assertThat(ese.getHeader("WWW-Authenticate"), contains(schemes));