Return correct HTTP headers on security exceptions

* Changed Authentication/AuthorizationException to always return RestStatus.UNAUTHORIZED
* Added the WWW-Authenticate header, which results in user/pass input in browsers
* Added tests and own assertions class

Closes elastic/elasticsearch#18

Original commit: elastic/x-pack-elasticsearch@c6ce084692
This commit is contained in:
Alexander Reelsen 2014-08-18 13:28:07 +02:00
parent 1f5f3f21f9
commit 452367b674
6 changed files with 64 additions and 4 deletions

View File

@ -5,19 +5,25 @@
*/
package org.elasticsearch.shield;
import org.elasticsearch.ElasticsearchException;import java.lang.String;import java.lang.Throwable;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import java.util.List;
/**
*
*/
public class SecurityException extends ElasticsearchException {
public class SecurityException extends ElasticsearchException.WithRestHeaders {
public static final ImmutableMap<String, List<String>> HEADERS = ImmutableMap.<String, List<String>>builder().put("WWW-Authenticate", Lists.newArrayList("Basic realm=\""+ SecurityPlugin.NAME +"\"")).build();
public SecurityException(String msg) {
super(msg);
super(msg, HEADERS);
}
public SecurityException(String msg, Throwable cause) {
super(msg, cause);
super(msg, cause, HEADERS);
}
}

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.shield.authc;
import org.elasticsearch.rest.RestStatus;
/**
*
*/
@ -17,4 +19,9 @@ public class AuthenticationException extends org.elasticsearch.shield.SecurityEx
public AuthenticationException(String msg, Throwable cause) {
super(msg, cause);
}
@Override
public RestStatus status() {
return RestStatus.UNAUTHORIZED;
}
}

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.shield.authz;
import org.elasticsearch.rest.RestStatus;
/**
*
*/
@ -17,4 +19,9 @@ public class AuthorizationException extends org.elasticsearch.shield.SecurityExc
public AuthorizationException(String msg, Throwable cause) {
super(msg, cause);
}
@Override
public RestStatus status() {
return RestStatus.UNAUTHORIZED;
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield.authc;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.test.ElasticsearchTestCase;
@ -13,6 +14,7 @@ import org.elasticsearch.transport.TransportMessage;
import org.junit.Before;
import org.junit.Test;
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
@ -62,6 +64,7 @@ public class InternalAuthenticationServiceTests extends ElasticsearchTestCase {
fail("expected authentication exception with missing auth token");
} catch (AuthenticationException ae) {
assertThat(ae.getMessage(), equalTo("Missing authentication token for request [_action]"));
assertContainsWWWAuthenticateHeader(ae);
}
verify(auditTrail).anonymousAccess("_action", message);
verifyNoMoreInteractions(auditTrail);

View File

@ -7,10 +7,12 @@ package org.elasticsearch.shield.authc.support;
import com.google.common.base.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.shield.authc.AuthenticationException;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportRequest;
import org.junit.Test;
import static org.elasticsearch.shield.test.ShieldAssertions.assertContainsWWWAuthenticateHeader;
import static org.hamcrest.Matchers.*;
/**
@ -49,4 +51,17 @@ public class UsernamePasswordTokenTests extends ElasticsearchTestCase {
UsernamePasswordToken token2 = UsernamePasswordToken.extractToken(request, null);
assertThat(token, is(token2));
}
@Test
public void testThatAuthorizationExceptionContainsResponseHeaders() {
TransportRequest request = new TransportRequest() {};
String header = "BasicBroken";
request.putHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, header);
try {
UsernamePasswordToken.extractToken(request, null);
fail("Expected exception but did not happen");
} catch (AuthenticationException e) {
assertContainsWWWAuthenticateHeader(e);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.test;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.SecurityException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class ShieldAssertions {
public static void assertContainsWWWAuthenticateHeader(org.elasticsearch.shield.SecurityException e) {
assertThat(e.status(), is(RestStatus.UNAUTHORIZED));
assertThat(e.getHeaders(), hasKey("WWW-Authenticate"));
assertThat(e.getHeaders().get("WWW-Authenticate"), hasSize(1));
assertThat(e.getHeaders().get("WWW-Authenticate").get(0), is(SecurityException.HEADERS.get("WWW-Authenticate").get(0)));
}
}