Fixing X-Forwarded-Host header handling

This commit is contained in:
Martin Stockhammer 2017-05-10 22:59:51 +02:00
parent 396694765f
commit 4e4e3428c4
2 changed files with 175 additions and 102 deletions

View File

@ -31,6 +31,7 @@ import org.apache.archiva.redback.integration.filter.authentication.basic.HttpBa
import org.apache.archiva.redback.policy.AccountLockedException; import org.apache.archiva.redback.policy.AccountLockedException;
import org.apache.archiva.redback.policy.MustChangePasswordException; import org.apache.archiva.redback.policy.MustChangePasswordException;
import org.apache.archiva.redback.users.User; import org.apache.archiva.redback.users.User;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Message; import org.apache.cxf.message.Message;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -343,7 +344,7 @@ public class RequestValidationInterceptor
catch ( MalformedURLException ex ) catch ( MalformedURLException ex )
{ {
log.error( "Configured baseUrl (rest.baseUrl={}) is invalid. Message: {}", baseUrlStr, log.error( "Configured baseUrl (rest.baseUrl={}) is invalid. Message: {}", baseUrlStr,
ex.getMessage() ); ex.getMessage() );
} }
} }
} }
@ -405,7 +406,7 @@ public class RequestValidationInterceptor
if ( noHeader && denyAbsentHeaders ) if ( noHeader && denyAbsentHeaders )
{ {
log.warn( "Request denied. No Origin or Referer header found and {}=true", log.warn( "Request denied. No Origin or Referer header found and {}=true",
UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY ); UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY );
containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() );
return; return;
} }
@ -483,7 +484,7 @@ public class RequestValidationInterceptor
if ( !td.isValid() || !td.getUser().equals( username ) ) if ( !td.isValid() || !td.getUser().equals( username ) )
{ {
log.error( "Invalid data in validation token header {} for user {}: isValid={}, username={}", log.error( "Invalid data in validation token header {} for user {}: isValid={}, username={}",
X_XSRF_TOKEN, username, td.isValid(), td.getUser() ); X_XSRF_TOKEN, username, td.isValid(), td.getUser() );
containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() );
} }
} }
@ -535,15 +536,22 @@ public class RequestValidationInterceptor
{ {
xforwardedProto = requestUrl.getProtocol(); xforwardedProto = requestUrl.getProtocol();
} }
if ( xforwarded != null )
if ( xforwarded != null && !StringUtils.isEmpty( xforwarded ) )
{ {
try // X-Forwarded-Host header may contain multiple hosts if there is
// more than one proxy between the client and the server
String[] forwardedList = xforwarded.split( "\\s*,\\s*" );
for ( String hostname : forwardedList )
{ {
urls.add( new URL( xforwardedProto + "://" + xforwarded ) ); try
} {
catch ( MalformedURLException ex ) urls.add( new URL( xforwardedProto + "://" + hostname ) );
{ }
log.warn( "X-Forwarded-Host Header is malformed: {}", ex.getMessage() ); catch ( MalformedURLException ex )
{
log.warn( "X-Forwarded-Host Header is malformed: {}", ex.getMessage() );
}
} }
} }
return urls; return urls;

View File

@ -38,169 +38,234 @@ import java.util.List;
/** /**
* Created by Martin Stockhammer on 21.01.17. * Created by Martin Stockhammer on 21.01.17.
* * <p>
* Unit Test for RequestValidationInterceptor. The unit tests are all without token validation. * Unit Test for RequestValidationInterceptor. The unit tests are all without token validation.
*
*/ */
@RunWith(JUnit4.class) @RunWith( JUnit4.class )
public class RequestValidationInterceptorTest extends TestCase { public class RequestValidationInterceptorTest extends TestCase
{
@Test @Test
public void validateRequestWithoutHeader() throws UserConfigurationException, IOException { public void validateRequestWithoutHeader() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertTrue(ctx.isAborted()); assertTrue( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithOrigin() throws UserConfigurationException, IOException { public void validateRequestWithOrigin() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test.archiva.org"); request.setServerName( "test.archiva.org" );
request.addHeader("Origin","http://test.archiva.org/myservlet"); request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertFalse(ctx.isAborted()); assertFalse( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithBadOrigin() throws UserConfigurationException, IOException { public void validateRequestWithBadOrigin() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test.archiva.org"); request.setServerName( "test.archiva.org" );
request.addHeader("Origin","http://test2.archiva.org/myservlet"); request.addHeader( "Origin", "http://test2.archiva.org/myservlet" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertTrue(ctx.isAborted()); assertTrue( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithReferer() throws UserConfigurationException, IOException { public void validateRequestWithReferer() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test.archiva.org"); request.setServerName( "test.archiva.org" );
request.addHeader("Referer","http://test.archiva.org/myservlet2"); request.addHeader( "Referer", "http://test.archiva.org/myservlet2" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertFalse(ctx.isAborted()); assertFalse( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithBadReferer() throws UserConfigurationException, IOException { public void validateRequestWithBadReferer() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test.archiva.org"); request.setServerName( "test.archiva.org" );
request.addHeader("Referer","http://test3.archiva.org/myservlet2"); request.addHeader( "Referer", "http://test3.archiva.org/myservlet2" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertTrue(ctx.isAborted()); assertTrue( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithOriginAndReferer() throws UserConfigurationException, IOException { public void validateRequestWithOriginAndReferer() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test.archiva.org"); request.setServerName( "test.archiva.org" );
request.addHeader("Origin","http://test.archiva.org/myservlet"); request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
request.addHeader("Referer","http://test.archiva.org/myservlet2"); request.addHeader( "Referer", "http://test.archiva.org/myservlet2" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertFalse(ctx.isAborted()); assertFalse( ctx.isAborted() );
}
@Test
public void validateRequestWithOriginAndRefererAndXForwarded() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName( "xxx.archiva.org" );
request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
request.addHeader( "Referer", "http://test.archiva.org/myservlet2" );
request.addHeader( "X-Forwarded-Host", "test.archiva.org" );
interceptor.setHttpRequest( request );
interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter( ctx );
assertFalse( ctx.isAborted() );
}
@Test
public void validateRequestWithOriginAndRefererAndWrongXForwarded() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName( "xxx.archiva.org" );
request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
request.addHeader( "Referer", "http://test.archiva.org/myservlet2" );
request.addHeader( "X-Forwarded-Host", "test2.archiva.org" );
interceptor.setHttpRequest( request );
interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter( ctx );
assertTrue( ctx.isAborted() );
}
@Test
public void validateRequestWithOriginAndRefererAndXForwardedMultiple() throws UserConfigurationException, IOException
{
TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName( "xxx.archiva.org" );
request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
request.addHeader( "Referer", "http://test.archiva.org/myservlet2" );
request.addHeader( "X-Forwarded-Host", "my.proxy.org, test.archiva.org:80" );
interceptor.setHttpRequest( request );
interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter( ctx );
assertFalse( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithOriginAndStaticUrl() throws UserConfigurationException, IOException { public void validateRequestWithOriginAndStaticUrl() throws UserConfigurationException, IOException
{
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
List<String> urls = new ArrayList<String>(); List<String> urls = new ArrayList<String>();
urls.add("http://test.archiva.org"); urls.add( "http://test.archiva.org" );
cfg.addList("rest.baseUrl",urls); cfg.addList( "rest.baseUrl", urls );
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("test4.archiva.org"); request.setServerName( "test4.archiva.org" );
request.addHeader("Origin","http://test.archiva.org/myservlet"); request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertFalse(ctx.isAborted()); assertFalse( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithBadOriginAndStaticUrl() throws UserConfigurationException, IOException { public void validateRequestWithBadOriginAndStaticUrl() throws UserConfigurationException, IOException
{
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
List<String> urls = new ArrayList<String>(); List<String> urls = new ArrayList<String>();
urls.add("http://mytest.archiva.org"); urls.add( "http://mytest.archiva.org" );
cfg.addList("rest.baseUrl",urls); cfg.addList( "rest.baseUrl", urls );
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("mytest.archiva.org"); request.setServerName( "mytest.archiva.org" );
request.addHeader("Origin","http://test.archiva.org/myservlet"); request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertTrue(ctx.isAborted()); assertTrue( ctx.isAborted() );
} }
@Test @Test
public void validateRequestWithOriginListAndStaticUrl() throws UserConfigurationException, IOException { public void validateRequestWithOriginListAndStaticUrl() throws UserConfigurationException, IOException
{
MockUserConfiguration cfg = new MockUserConfiguration(); MockUserConfiguration cfg = new MockUserConfiguration();
List<String> urls = new ArrayList<String>(); List<String> urls = new ArrayList<String>();
urls.add("http://mytest.archiva.org"); urls.add( "http://mytest.archiva.org" );
urls.add("http://mytest2.archiva.org"); urls.add( "http://mytest2.archiva.org" );
urls.add("http://test.archiva.org"); urls.add( "http://test.archiva.org" );
cfg.addList("rest.baseUrl",urls); cfg.addList( "rest.baseUrl", urls );
cfg.addValue(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION,"true"); cfg.addValue( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, "true" );
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg); RequestValidationInterceptor interceptor = new RequestValidationInterceptor( cfg );
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService"); MockHttpServletRequest request = new MockHttpServletRequest( "GET", "/api/v1/userService" );
request.setServerName("mytest.archiva.org"); request.setServerName( "mytest.archiva.org" );
request.addHeader("Origin","http://test.archiva.org/myservlet"); request.addHeader( "Origin", "http://test.archiva.org/myservlet" );
interceptor.setHttpRequest(request); interceptor.setHttpRequest( request );
interceptor.init(); interceptor.init();
MockContainerRequestContext ctx = new MockContainerRequestContext(); MockContainerRequestContext ctx = new MockContainerRequestContext();
interceptor.filter(ctx); interceptor.filter( ctx );
assertFalse(ctx.isAborted()); assertFalse( ctx.isAborted() );
} }
} }