Adding token validation to interceptor
diff --git a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
index 9fa02ce..4b3e6fa 100644
--- a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
+++ b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
@@ -136,7 +136,11 @@
# REST security settings
# REST base url is for avoiding CSRF attacks
+# Enable CSRF filtering
+rest.csrffilter.enabled=true
# If it is not set or empty it tries to determine the base url automatically
rest.baseUrl=
# If true, requests without Origin or Referer Header are denied
-rest.csrf.absentorigin.deny=true
\ No newline at end of file
+rest.csrffilter.absentorigin.deny=true
+# If true, the validation of the CSRF tokens will be disabled
+rest.csrffilter.disableTokenValidation=false
\ No newline at end of file
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index 182d23a..a9afede 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -19,7 +19,17 @@
*/
+import org.apache.archiva.redback.authentication.AuthenticationException;
+import org.apache.archiva.redback.authentication.AuthenticationResult;
+import org.apache.archiva.redback.authentication.InvalidTokenException;
+import org.apache.archiva.redback.authentication.TokenData;
+import org.apache.archiva.redback.authentication.TokenManager;
+import org.apache.archiva.redback.authorization.RedbackAuthorization;
import org.apache.archiva.redback.configuration.UserConfiguration;
+import org.apache.archiva.redback.integration.filter.authentication.basic.HttpBasicAuthentication;
+import org.apache.archiva.redback.policy.AccountLockedException;
+import org.apache.archiva.redback.policy.MustChangePasswordException;
+import org.apache.archiva.redback.users.User;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Message;
import org.slf4j.Logger;
@@ -60,15 +70,18 @@
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String X_FORWARDED_HOST = "X-Forwarded-Host";
+ private static final String X_XSRF_TOKEN = "X-XSRF-TOKEN";
private static final String ORIGIN = "Origin";
private static final String REFERER = "Referer";
- private static final String CFG_REST_BASE_URL = "rest.baseUrl";
- private static final String CFG_REST_CSRF_ABSENTORIGIN_DENY = "rest.csrffilter.absentorigin.deny";
- private static final String CFG_REST_CSRF_ENABLED = "rest.csrffilter.enabled";
+ public static final String CFG_REST_BASE_URL = "rest.baseUrl";
+ public static final String CFG_REST_CSRF_ABSENTORIGIN_DENY = "rest.csrffilter.absentorigin.deny";
+ public static final String CFG_REST_CSRF_ENABLED = "rest.csrffilter.enabled";
+ public static final String CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION = "rest.csrffilter.disableTokenValidation";
private final Logger log = LoggerFactory.getLogger( getClass() );
private boolean enabled = true;
+ private boolean checkToken = true;
private boolean useStaticUrl = false;
private boolean denyAbsentHeaders = true;
private URL baseUrl;
@@ -77,6 +90,14 @@
private UserConfiguration config;
@Inject
+ @Named( value = "httpAuthenticator#basic" )
+ private HttpBasicAuthentication httpAuthenticator;
+
+ @Inject
+ @Named( value = "tokenManager#default")
+ TokenManager tokenManager;
+
+ @Inject
public RequestValidationInterceptor(@Named( value = "userConfiguration#default" )
UserConfiguration config) {
this.config = config;
@@ -100,6 +121,7 @@
if (!enabled) {
log.info("CSRF Filter is disabled by configuration");
}
+ checkToken = !config.getBoolean(CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION, false);
}
@Override
@@ -110,14 +132,60 @@
if (targetUrl == null) {
log.error("Could not verify target URL.");
containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ return;
}
if (!checkSourceRequestHeader(targetUrl, request)) {
log.warn("HTTP Header check failed. Assuming CSRF attack.");
containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ return;
+ }
+
+ if (checkToken) {
+ checkValidationToken(containerRequestContext, request);
}
}
}
+ private void checkValidationToken(ContainerRequestContext containerRequestContext, HttpServletRequest request) {
+ Message message = JAXRSUtils.getCurrentMessage();
+ RedbackAuthorization redbackAuthorization = getRedbackAuthorization(message);
+ // We check only services that are restricted
+ if (!redbackAuthorization.noRestriction()) {
+ String tokenString = request.getHeader(X_XSRF_TOKEN);
+ if (tokenString==null || tokenString.length()==0) {
+ log.warn("No validation token header found: {}",X_XSRF_TOKEN);
+ containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ return;
+ }
+
+ try {
+ TokenData td = tokenManager.decryptToken(tokenString);
+ AuthenticationResult auth = getAuthenticationResult(message, request);
+ if (auth==null) {
+ log.error("Not authentication data found");
+ containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ return;
+ }
+ User loggedIn = auth.getUser();
+ if (loggedIn==null) {
+ log.error("User not logged in");
+ containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ return;
+ }
+ String username = loggedIn.getUsername();
+ if (!td.isValid() || !td.getUser().equals(username)) {
+ log.error("Invalid data in validation token header {} for user {}: isValid={}, username={}",
+ X_XSRF_TOKEN, username, td.isValid(), td.getUser());
+ containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ }
+ } catch (InvalidTokenException e) {
+ log.error("Token validation failed {}", e.getMessage());
+ containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+ }
+ }
+ log.debug("Token validated");
+ }
+
private HttpServletRequest getRequest() {
if (httpRequest!=null) {
return httpRequest;
@@ -215,4 +283,33 @@
public void setHttpRequest(HttpServletRequest request) {
this.httpRequest = request;
}
+
+ private AuthenticationResult getAuthenticationResult(Message message, HttpServletRequest request) {
+ AuthenticationResult authenticationResult = message.get(AuthenticationResult.class);
+
+ log.debug("authenticationResult from message: {}", authenticationResult);
+ if ( authenticationResult == null )
+ {
+ try
+ {
+ authenticationResult =
+ httpAuthenticator.getAuthenticationResult( request, getHttpServletResponse( message ) );
+
+ log.debug( "authenticationResult from request: {}", authenticationResult );
+ }
+ catch ( AuthenticationException e )
+ {
+ log.debug( "failed to authenticate for path {}", message.get( Message.REQUEST_URI ) );
+ }
+ catch ( AccountLockedException e )
+ {
+ log.debug( "account locked for path {}", message.get( Message.REQUEST_URI ) );
+ }
+ catch ( MustChangePasswordException e )
+ {
+ log.debug( "must change password for path {}", message.get( Message.REQUEST_URI ) );
+ }
+ }
+ return authenticationResult;
+ }
}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
index c88492a..27ab531 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
@@ -20,10 +20,12 @@
import junit.framework.TestCase;
+import org.apache.archiva.redback.authentication.TokenManager;
import org.apache.archiva.redback.configuration.UserConfigurationException;
import org.apache.archiva.redback.rest.services.interceptors.RequestValidationInterceptor;
import org.apache.archiva.redback.rest.services.mock.MockContainerRequestContext;
import org.apache.archiva.redback.rest.services.mock.MockUserConfiguration;
+import org.apache.archiva.redback.system.SecuritySystem;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -36,7 +38,7 @@
/**
* Created by Martin Stockhammer on 21.01.17.
*
- * Unit Test for RequestValidationInterceptor.
+ * Unit Test for RequestValidationInterceptor. The unit tests are all without token validation.
*
*/
@RunWith(JUnit4.class)
@@ -46,7 +48,9 @@
@Test
public void validateRequestWithoutHeader() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest();
interceptor.setHttpRequest(request);
@@ -58,7 +62,9 @@
@Test
public void validateRequestWithOrigin() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test.archiva.org");
@@ -72,7 +78,9 @@
@Test
public void validateRequestWithBadOrigin() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test.archiva.org");
@@ -86,7 +94,9 @@
@Test
public void validateRequestWithReferer() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test.archiva.org");
@@ -100,7 +110,9 @@
@Test
public void validateRequestWithBadReferer() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test.archiva.org");
@@ -114,7 +126,9 @@
@Test
public void validateRequestWithOriginAndReferer() throws UserConfigurationException, IOException {
+ TokenManager tm = new TokenManager();
MockUserConfiguration cfg = new MockUserConfiguration();
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test.archiva.org");
@@ -132,6 +146,8 @@
public void validateRequestWithOriginAndStaticUrl() throws UserConfigurationException, IOException {
MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue("rest.baseUrl","http://test.archiva.org");
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
+ TokenManager tm = new TokenManager();
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("test4.archiva.org");
@@ -147,6 +163,8 @@
public void validateRequestWithBadOriginAndStaticUrl() throws UserConfigurationException, IOException {
MockUserConfiguration cfg = new MockUserConfiguration();
cfg.addValue("rest.baseUrl","http://mytest.archiva.org");
+ cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
+ TokenManager tm = new TokenManager();
RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
request.setServerName("mytest.archiva.org");