Expand my Community achievements bar.

Using OOTB-style Constraints in AEM on a Custom Form Component

Avatar

Level 1

I'm trying to validate a custom recaptcha component using constraints in the same way the OOTB Form Elements do.  I'm having trouble figuring out how to connect the custom component to it's servervalidation.jsp though.

I need help getting to the point where submitting a form with the custom constraint selected causes the captcha/servervalidations.jsp to validate.  It currently allows an undone captcha to be submitted.

I have my component:

(I made a lot of edits to the captcha.jsp for brevity here since it's not really the point and I may have messed it up, so if there's syntax errors in that file don't worry about it.)

captcha.jsp

    <%@include file="/apps/codebase/global.jsp" %>

  

    <div class="captcha-wrap">

      <div id="recaptcha${properties.reCaptchaId}" class="g-recaptcha"></div>  

    <script>

  

        /* Renders captchas.  Runs @ async defer (callback on recaptcha src script in 'basepage/footer.jsp') */

        function recaptchaCallback() {

          var cC = $('.g-recaptcha').length;  // captcha count

          if ( cC >= 1 && cC <= 6) {   // recaptcha1 loads when cC is 1-6

            grecaptcha.render('recaptcha1', {

              'sitekey' : '<PUBLIC_KEY>'

            });

          }

          if ( cC >= 2 && cC <=  6) {

            grecaptcha.render('recaptcha2', {

              'sitekey' : '<PUBLIC_KEY>'

            });

          }

          /* Additional if statements for rendering 1 to 6 captchas on the same page */

  

    </script>

This script renders the captcha:

footer.jsp

    <script src="https://www.google.com/recaptcha/api.js?onload=recaptchaCallback&render=explicit" defer></script>

Here is my dialog with the OOTB captcha constraint tab included

dialog.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"

        jcr:primaryType="cq:Dialog"

        title="Captcha Configuration"

        xtype="dialog">

        <items

            jcr:primaryType="cq:Widget"

            xtype="tabpanel">

            <items jcr:primaryType="cq:WidgetCollection">

                <tab0

                    jcr:primaryType="cq:Panel"

                    title="Captcha">

                    <items jcr:primaryType="cq:WidgetCollection">

                        <reCaptchaId

                        jcr:primaryType="cq:Widget"

                        fieldDescription="Must be 1, 2, 3, 4, 5 or 6. Must go in order, i.e. don't only have a 1, 2, and 4 on the page or it won't work. Will not work with 7+ either."

                        fieldLabel="ReCaptcha ID"

                        name="./reCaptchaId"

                        xtype="textfield" />

                    </items>

                </tab0>

            <tab1

               jcr:primaryType="nt:unstructured"

               title="Constraints"

               xtype="panel">

               <items jcr:primaryType="cq:WidgetCollection">

                    <required

                         jcr:primaryType="cq:Widget"

                         fieldLabel="Required"

                         inputValue="true"

                         name="./required"

                         type="checkbox"

                         xtype="selection"/>

                   <requiredMessage

                         jcr:primaryType="cq:Widget"             

                         fieldLabel="Required Message"

                         name="./requiredMessage"

                         xtype="textarea"/>

                    <constraintType

                         jcr:primaryType="cq:Widget"

                         fieldLabel="Constraint"

                         name="./constraintType"

                         options="/etc/designs/codebase/options/constraints.json"

                         type="select"

                         xtype="selection"/>

                   <constraintMessage

                        jcr:primaryType="cq:Widget"

                        fieldLabel="Constraint Message"

                        name="./constraintMessage"

                        xtype="textarea"/>

                </items>

             </tab1> 

            </items>

        </items>

    </jcr:root>

And here is the json referenced:

constraints.json

    [

         {"value":"","text":"None"},

         {"value":"codebase/components/content/formelements/constraints/captcha","text":"Captcha"}

    ]

Which brings me to the constraint file itself.  My problem is that I cannot get this file to actually validate against the captcha.jsp file.  So it has not been debugged and may be full of java errors for all I know.

This file is a combination of the OOTB file found at /libs/foundation/components/form/captcha/servervalidation.jsp<br>

and the captcha validation solution found here: Server side validation for reCAPTCHA V2 or Invisible reCAPTCHA with Java (Servlet) - Code Review Sta...

codebase/components/content/formelements/constraints/captcha/servervalidation.jsp

    <%@page session="false" %><%

    %><%@page import="java.io.BufferedReader,

    java.io.InputStream;

                    java.text.ParsePosition,

        java.io.InputStreamReader,

    java.net.URL,

    java.nio.charset.Charset,

    org.json.JSONObject,

                    com.day.cq.wcm.foundation.forms.FieldHelper,

                    com.day.cq.wcm.foundation.forms.FieldDescription,

                    com.day.cq.wcm.foundation.forms.FormsHelper,

                    com.day.cq.wcm.foundation.forms.ValidationInfo"%><%

    %><%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0" %>

  

    <sling:defineObjects/><%

  

        // Get field description and force its name

        FieldDescription desc = FieldHelper.getConstraintFieldDescription(slingRequest);

        desc.setName(":cq:recaptcha");

  

        // Check if a value has been provided

        if (FieldHelper.checkRequired(slingRequest, desc)) {

            final String response = request.getParameter("g-recaptcha-response");

            final String secretKey = "<PRIVATE_KEY>";

  

    private static boolean isCaptchaValid(String secretKey, String response) {

        try {

            String url = "https://www.google.com/recaptcha/api/siteverify?"

                    + "secret=" + secretKey

                    + "&response=" + response;

            InputStream res = new URL(url).openStream();

            BufferedReader rd = new BufferedReader(new InputStreamReader(res, Charset.forName("UTF-8")));

  

            StringBuilder sb = new StringBuilder();

            int cp;

            while ((cp = rd.read()) != -1) {

                sb.append((char) cp);

            }

            String jsonText = sb.toString();

            res.close();

  

            JSONObject json = new JSONObject(jsonText);

            return json.getBoolean("success");

        } catch (Exception e) {

            return false;

        }

    }

            if (!isCaptchaValid("<PRIVATE_KEY>", request.getParameter("g-recaptcha-response"))) {

                ValidationInfo.addConstraintError(slingRequest, desc);

            }

        }

  

    %>

How can I get this captcha constraint `servervalidation.jsp` file to actually work? Currently the captcha can be submitted uncompleted and no validation stops it.

I do notice some form parameters in the `_cq_editConfig.xml` of the OOTB components, and that's the only thing I notice that I haven't copied to try to get this constraint to work. But that's because I'm not sure how it works.

11 Replies

Avatar

Level 1

smacdonald2008 unless I'm misunderstanding something that article is out of date.  I did that entire tutorial before and it only verifies the deprecated reCaptcha V1 while just rendering the v2 underneath it.  That's why I'm trying to get a custom constraint to work.  I'm certainly open to other paths but I don't think that works, and update would be appreciated.

If you look at the article, rendering the v2 under v1 makes no sense and there's no verification set up for v2.  No js alerts either like there is for v1. And they only verify v1 in the video.  v1 is fully deprecated at of 3/31/2018, it no longer loads in the page at all.

Avatar

Level 10

Wow - when we wrote that - it worked - but been a while. We will look at updating it.

Avatar

Level 1

Do you think this custom constraint path could work?  Or do you know if developers can make custom constraints for custom components?  I'm having trouble finding anything online about recaptcha v2 and AEM because everyone just reblogs the article that you posted.  Any other suggestions would be helpful, I need this validated server-side.

Avatar

Level 10

Its been nearly 2 years since that was written. I have not looked at the new version. I will talk to internal ppl and see if we can update that article to use the newest one.

Avatar

Level 10

As an FYI... we tested that article on 6.4 and it works fine.

recaptcha.png

Avatar

Level 1

smacdonald2008 As far as I can tell the ajax call to verify the reCaptcha is only triggered in that example by v1.  For example, `var capResponse=$('#recaptcha_response_field').val();` is returning "undefined" for me when using v2, and its length decides if the ajax call is triggered via the if/else logic in capTemplate.jsp.

When I click the checkbox next to "I'm not a robot" on v2 and then click the "test" button above it, I get the "response is empty" message, meaning that capResponse is undefined.  I don't see any alerts in your screenshot that say the recaptcha was verified through the ajax call, although it could have.  In my demo I'm not seeing that the ajax call or any of the verification in validateCaptcha.jsp is being triggered with reCaptcha v2.

Overall, based on what I've read, an ajax recaptcha verification isn't recommended because a hacker can brute-force values into the ajax call.  So even if v2 is being validated via Ajax and I'm just misunderstanding, an updated article that outlines sending a POST to https://www.google.com/recaptcha/api/siteverify?secret=<secretKey>&response=<response> in order to receive a JSON verification response from Google within an AEM environment would be extremely helpful.  It's outlined in the recaptcha v2 documentation here and it's attempted in my example above, although doing so inside an OOTB-style constraint may be misguided.  I'm just having trouble getting the validations to run server-side on submit.

Avatar

Level 1

smacdonald2008​ were you able to verify that the Ajax called was being fired by the reCaptcha v2?

Avatar

Level 10

Test version 2 on a non-AEM site - ie - Tomcat and see if its working.

Avatar

Level 1

smacdonald2008 The issues with the example code and v2 aren't because of the environment or AEM, it's because it's not set up to verify v2 like I explained above.  The Ajax call is only triggered if capResponse has a value via .val(),  and it can only get a value that way on v1.  There are several other variables included in the call that also don't have a value in v2. 

On v2, you get the response value with grecaptcha.getResponse(); or request.getParameter("g-recaptcha-response").  You then need to send a POST to google like they say in the docs, which the example code isn't doing: Verifying the user's response  |  reCAPTCHA       |  Google Developers

The validatecaptcha.jsp in the example code is asking for a user response and a challenge field to send in the recaptcha.checkAnswer() call and sends the key via recaptcha.setPrivateKey().  With v2 you send your private key and the just the g-recaptcha-response to "https://www.google.com/recaptcha/api/siteverify", the 'user response' doesn't exist.

The call url looks like this:

String url = "https://www.google.com/recaptcha/api/siteverify?"
  
+ "secret=" + secretKey
  
+ "&response=" + response;

I'm in the process of creating one for v2.  If I'm misunderstanding and the Adobe site example does work for v2 I apologize for the long-winded explanations, I don't think the example code works though.