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

andrewh9285432

29-06-2018

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.

Replies

Highlighted

andrewh9285432

29-06-2018

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.

Highlighted

andrewh9285432

29-06-2018

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.

Highlighted

smacdonald2008

29-06-2018

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.

Highlighted

andrewh9285432

02-07-2018

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.