Donnerstag, 28. Juli 2011

How to build and modfiy Tapestry 5.3.0

Currently I think the fastest and easy way to build your custom Tapestry version is to do a checkout from the subversion repository. Use gradle idea to generate IntelliJ IDEA project description files. The community version of this IDE is free. Just open the project and everything should compile. Since the switch to a gradle build process, the eclipse integration is a little bit broken (at least for me). The gradle generated eclipse project files didn't work for me. Compared to that the IntelliJ IDEA integration is painless. Using gradle install deploys your build in your local maven repository which makes it available to your Tapestry projects.

So the required commands in a brief are:

  • svn checkout https://svn.apache.org/repos/asf/tapestry/tapestry5/trunk tapestry-project
  • cd tapestry-project
  • gradle idea
  • gradle install

Donnerstag, 30. Juni 2011

Tapestry 5.3.0 + Chenillekit + Jumpstart

In order to test my CSRF protection I want to use Jumpstart as testcase. This requires an update of the Chenillekit and Jumpstart to work with Apache Tapestry 5.3.0.

Chenillekit update:
The required modifications in the source code are quite easy. I was able to make a compatible build and  deployed it to my Google Summer of Code maven repository. This is just an interim solution until an official Chenillekit update has happend.

Jumpstart update:
  • Libraries: The Tapestry and Chenillekit libraries in the web/src/main/lib-compile and web/src/main/lib-runtime have to be updated.
  • Code: The AppModule contributeValidateMessagesSource method has to be renamed to contributeComponentMessagesSource
That's it.

Update:
[TAP5-1440] - Remove "validateForm" event triggered by Form component (replaced with "validate" event) --> onValidateForm method needs to be renamed!






Sonntag, 5. Juni 2011

Protecting AJAX requests

So far the insert of an Anti-CSRF token works for every type of component. The token check can either be inserted by using the @Protected annotation at a page or on an event handling method. A problem with the redirect-after-post approach is that the following GET request after a form POST cannot be enhanced with an Anti-CSRF token. I will try to solve this problem later, next task is to protect Ajax based requests.








Samstag, 4. Juni 2011

Fine grained protection mechanism

I changed the design of the fine grained protection mechanism. The insertCSRFToken method and logic is moved to the components, by using AOP techniques. A class transformation inserts component specific methods for the internal Tapestry components, which rely on the specific components. Additionally a generic XPath insertCSRFToken can be used for any kind of component. The mixin is then quite easy and calls the insertCSRFToken method. To make this possible all modified components implement the CSRFProtectable interface.


The demonstration app contains now also a BeanEditForm, that introduces some new problems. The @Protected annotation needs to work also on page level, because on components used in templates it is not possible to add an annotation in the event handler.

Samstag, 14. Mai 2011

Apache Tapestry - CSRF Protection - First Prototype

To get in touch with tapestry internals I implemented a first draft of a CSRF protection mechanism based on a mixin and an annotation named Protected.

The mixin adds a hidden formtoken input field into a Form component - currently in a really ugly way. Additionally the formtoken is also stored in the session at the server side.

Protected - Mixin

package org.apache.tapestry5.csrfprotection.victimapp.mixins;

import java.util.List;
import java.util.UUID;

import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.dom.Node;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;

public class Protected {
 @Inject
 private Request request;

 void afterRender(MarkupWriter writer) {
  List<Node> nodes = writer.getElement().getChildren();
  for(Node node:nodes){   
   if(node instanceof Element && ((Element)node).getName().equals("form")){
    Element form = (Element) node;
    String token = UUID.randomUUID().toString();
    form.element("input", "type","hidden","name","formtoken","value",token);
    request.getSession(true).setAttribute("formtoken", token);
   }
  }
 } 
}

The value of the formtoken stored in the session is compared against the HTTP request parameter, if the event handler is annotated with the Protected annotation.

Protected - Annotation


package org.apache.tapestry5.csrfprotection.victimapp.annotations;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface Protected {

}


The magic happens in the ProtectedWorker class, where the class transformation is done with the help of Plastic. The methods that have the @Protected annotation are enhanced with the comparison of the session parameter and the request parameter. In the case of an CSRF attack an exception is thrown.

ProtectedWorker

package org.apache.tapestry5.csrfprotection.victimapp.protection;

import org.apache.tapestry5.csrfprotection.victimapp.annotations.Protected;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticMethod;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Session;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
import org.apache.tapestry5.plastic.MethodInvocation;

public class ProtectedWorker implements ComponentClassTransformWorker2 {
 private Request request;
 
 public ProtectedWorker(Request request){
  this.request = request;
 }
 public void transform(PlasticClass plasticClass,
   TransformationSupport support, MutableComponentModel model) {

  final MethodAdvice myLoggingAdvice = new MethodAdvice() {
   public void advise(MethodInvocation invocation) {
    
    String requestParam = request.getParameter("formtoken");
    String sessionParam = ""; // avoid null pointer exceptions
    Session session = request.getSession(false);
    if(session != null){
     sessionParam = session.getAttribute("formtoken").toString();
    }
    
    if(sessionParam.equals(requestParam)){
     invocation.proceed();
    }
    else{
     invocation.setCheckedException(new ProtectionException("CSRF Attack detected."));
     invocation.rethrow();
    }
   }
  };
  for (final PlasticMethod method : plasticClass
    .getMethodsWithAnnotation(Protected.class)) {
   method.addAdvice(myLoggingAdvice);
  }
 }
}

The exception class is currently trivial.

ProtectedException

package org.apache.tapestry5.csrfprotection.victimapp.protection;

public class ProtectedException extends Exception {
 public ProtectedException(String msg){
  super(msg);
 }

}


To make it run, the class transform configuratoin in the AppModule is required.

AppModule - Fragment

@Contribute(ComponentClassTransformWorker2.class)
    public static void provideTransformWorkers(
            OrderedConfiguration configuration,

            MetaWorker metaWorker,

            ComponentClassResolver resolver)
    {
     configuration.addInstance("Protected",ProtectedWorker.class);
    }

I just added the mixin in the html template of my sample applications and protected the onSuccess event handler.

Usage

<form t:type="form" t:id="statusForm" t:mixins="Protected">
@Protected
 private Object onSuccess(){
  history.getHistory().add(statusMessage);
  return this;
 }

CSRF - Attack

The sample attack that targets the form submit fails now with an exception:



Mittwoch, 11. Mai 2011

m2eclipse import does not recognize Java projects

m2eclipse seem not to work so nice as thought after the first usage. Some projects are not recognized as Java projects.