Tapestry : Using reCaptcha

There is already Captcha support build-in for Tapestry5 but doing something from scratch is always fun in Tapestry. So in this post we are going to use reCaptcha with Tapestry5. There are two ways of integrating reCaptcha in your website. One way is to add it statically and other using Ajax. We are going to use the former case.

Please go through the instructions here before proceeding.

We first put the required static content in the form of a component

<t:container xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>
    <script type="text/javascript"
            src="http://www.google.com/recaptcha/api/challenge?k=${publicKey}">
    </script>
    <noscript>
        <iframe src="http://www.google.com/recaptcha/api/noscript?k=${publicKey}"
                height="300" width="500" frameborder="0"></iframe>
        <br/>
        <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
        <input type="hidden" name="recaptcha_response_field"
               value="manual_challenge"/>
    </noscript>
</t:container>
public class ReCaptcha {
    @Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false, required = true)
    private String privateKey;

    @Parameter(defaultPrefix = BindingConstants.LITERAL, allowNull = false, required = true)
    @Property
    private String publicKey;

    @Parameter
    private boolean valid;

    @Parameter
    private String error;

    @Inject
    private FormSupport formSupport;

    @Inject
    private ComponentResources resources;

    @Inject
    private Request request;

    @Inject
    private HttpServletRequest servletRequest;

    private static final ComponentAction<ReCaptcha> PROCESS_SUBMISSION_ACTION = new
        ProcessSubmissionAction();

    private static final String RECAPTCHA_RESPONSE_FIELD = "recaptcha_response_field";

    private static final String RECAPTCHA_CHALLENGE_FIELD = "recaptcha_challenge_field";

    private static final String VERIFY_URL = "http://www.google.com/recaptcha/api/verify";

    @SetupRender
    void addProcessSubmissionAction() {
        if(formSupport == null){
           throw new RuntimeException(String.format(
                 "Component %s must be enclosed by a Form component.",
                    resources.getCompleteId()));
        }

        formSupport.store(this, PROCESS_SUBMISSION_ACTION);
    }

    private static class ProcessSubmissionAction implements ComponentAction<ReCaptcha> {
        @Override
        public void execute(ReCaptcha component) {
            component.processSubmission();
        }
    }

    private void processSubmission() {
        String response = request.getParameter(RECAPTCHA_RESPONSE_FIELD);
        String challenge = request.getParameter(RECAPTCHA_CHALLENGE_FIELD);

        valid = verifyResponse(challenge, response, servletRequest.getRemoteAddr());
    }

    private boolean verifyResponse(String challenge, String response, String ip) {
        Map<String, String> parameters = new HashMap<String, String>();

        parameters.put("privatekey", privateKey);
        parameters.put("challenge", challenge);
        parameters.put("response", response);
        parameters.put("remoteip", ip);
        error = post(parameters);

        return error == null;
    }

    public String post(Map<String, String> parameters) {
        try {
            HttpURLConnection connection = 
                (HttpURLConnection) new URL(VERIFY_URL).openConnection();
            String data = "";

            for(String key : parameters.keySet()) {
                data += key + "=" + parameters.get(key) + "&";
            }

            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");

            PrintWriter writer = new PrintWriter(connection.getOutputStream());

            writer.write(data);
            writer.flush();

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(connection.getInputStream()));

            String status = reader.readLine();
            String error = null;

            if("false".equalsIgnoreCase(status)) {
                error = reader.readLine();
            }
            connection.disconnect();
            
            return error;


        } catch(Exception ex) {
            throw new RuntimeException("Could not post to : " + VERIFY_URL, ex);
        }

    }
}

The important thing here to notice is that we need to process the submitted values. For that we inject FormSupport and store a ComponentAction into it. This action is executed on submission. Our action(ProcessionSubmissionAction) just executes processSubmission() method which in turn gets the submitted values and sends them as a POST request to the verification url.

The verification url returns first line as “true” or “false” depending upon whether the match was found or not. The second line is an error in case there is a mismatch.

The private key and public key are passed as arguments. Alternatively these can be set as Factory Defaults.

Usage is simple. Include it in your template inside a form.


<form t:type='form' t:id='myForm'>
   ....   

   <div t:type='recaptcha' publickey='my_public_key' 
        privatekey='my_private_key' valid='captchaValid'></div>

   ....
</form>

and then validate in your class.


@Property
private boolean captchaValid;

OnEvent(value = "validate", component = "myForm")
void validate(){
   if(!captchaValid){
      throw ValidationException("Captcha did not match");
   }
}

About these ads

Tagged: , ,

5 thoughts on “Tapestry : Using reCaptcha

  1. Walid H. November 5, 2011 at 6:50 AM Reply

    Thanks a lot,
    It works well for me. I just replace variables my_public_key and my_private_key with syntas like ${my_public_key}.
    By the way do you know if it is ok to put the private key in the .tml file ? I preferred to use @property instead.
    cheers,

    Walid

    • tawus November 5, 2011 at 7:25 AM Reply

      Hi Walid

      I would use the Symbols. Contribute it using contributeApplicationDefaults() and the use ${symbol:mykey} in the page

      • Walid H. November 5, 2011 at 5:05 PM

        Worked fine with Symbols, and it’s a more elegant way. thanks.

  2. [...] Source: http://tawus.wordpress.com/2011/10/14/tapestry-using-recaptcha/ [...]

  3. Jacob July 15, 2012 at 12:30 AM Reply

    Very nice article, if you would like to support ajax calls with tapestry zone you may consider to adjust script with following code from google api page:

    function showRecaptcha(element) {
    Recaptcha.create(“${publicKey}”, element, {
    theme: “clean”,
    callback: Recaptcha.focus_response_field});
    }

    and then initialization code before your form invocation:

    jQuery(document).ready(function() {
    showRecaptcha(‘recaptcha_div’);
    });

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 90 other followers

%d bloggers like this: