RecaptchaV2 on forms with multiple pages - can't navigate between form pages.
Hi,
This is an awesome package, we have a slight issue when implementing the RecaptchaV2 on forms that have multiple pages, adding the Recaptcha prevents navigation between pages of the form.
Have you spotted this before, any ideas how we can solve this?
I do not have much experience with multi page Umbraco Forms and have not seen this before. Does this happen with any fields that are mandatory and left empty when trying to navigate between pages? What about when using the default Recaptcha that comes with Forms? Does it still occur when the Recaptcha is checked or only when it's left empty?
This adds the recaptcha check each time the form is validated, so when moving from one page to the next it fires. Then since the Recaptcha is on the 2nd page, the validation fails, and the navigation fails.
I wonder how Umbraco.Forms does this with their version of the Recaptcha?, as this does allow navigation between pages.
Probably need to update the code, so it only fires to validate if the page that is being validated contains a recaptcha control.
Looks like this can be done via the FormValidationEventArgs.
I had exactly the same problem with the reCAPTCHA validating on a multi-step form. Fortunately, the contourPage div sits within a form, which has a FormStep hidden field, the value of which is the current form step.
As all our forms (and I guess most others) have the reCAPTCHA right at the end of the form, it can safely be assumed (as in the code below) that the control is on the last step.
<input name="FormStep" type="hidden" value="0">
So, all you need to do is check to see if the current step is equal to the total number of steps, THEN do the reCAPTCHA validation, giving the following source change in UmbracoEvents.cs
private void FormValidate(object sender, FormValidationEventArgs e)
{
//check to see which step we are at on the form (the last step will always contain the ReCaptcha control)
int loop1 = 0;
int currentStep = 0;
String[] arr1 = e.Context.Request.Form.AllKeys;
for (loop1 = 0; loop1 < arr1.Length; loop1++)
{
if (arr1[loop1] == "FormStep")
{
currentStep = Convert.ToInt16(e.Context.Request.Form["FormStep"]) + 1;
break;
}
}
//only try to validate a ReCaptcha control if we are on the last step
if (currentStep == e.Form.Pages.Count)
{
JToken jToken = null;
JToken jToken1 = null;
LogHelper.Info<UmbracoFormsEvents>("FormValidate with ReCaptcha Running...", new Func<object>[0]);
List<Field> list = (
from f in e.Form.AllFields
where f.FieldType.Name.Contains("GoogleReCaptcha")
select f).ToList<Field>();
if (list.Any<Field>())
{
Field field = list.FirstOrDefault<Field>();
HttpContext current = HttpContext.Current;
string setting = Configuration.GetSetting("RecaptchaPrivateKey");
if (setting != "")
{
string item = current.Request["g-recaptcha-response"];
string str = string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", setting, item);
bool flag = false;
List<string> strs = new List<string>();
//required to force TLS1.2 as TLS1.1 is disabled on the server
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (WebClient webClient = new WebClient())
{
JObject jObject = JObject.Parse(webClient.DownloadString(str));
if (jObject.TryGetValue("success", out jToken))
{
flag = Extensions.Value<bool>(jToken);
}
if (!jObject.TryGetValue("error-codes", out jToken1))
{
strs.Add("unknown-error");
}
else
{
JEnumerable<JToken> jEnumerable = jToken1.Children();
strs.AddRange(
from child in (IEnumerable<JToken>)jEnumerable
select Extensions.Value<string>(child));
}
}
if (!flag)
{
Controller controller = sender as Controller;
string str1 = ",";
foreach (string str2 in strs)
{
str1 = string.Concat(str1, ", ", str2);
}
str1 = str1.Replace(",,", "");
string str3 = string.Format("Recaptcha Verification Failed: {0}", str1);
if (field != null)
{
if (controller != null)
{
controller.ModelState.AddModelError(field.Id.ToString(), str3);
}
}
}
}
else
{
LogHelper.Warn<UmbracoFormsEvents>("ERROR: ReCaptcha v.2 is missing the Secret Key - Please update the '/app_plugins/umbracoforms/umbracoforms.config' to include 'key=\"RecaptchaPrivateKey\"'", new Func<object>[0]);
}
}
}
}
You will notice one additional line which may come in handy. We disabled TLS1.1 on our server which stopped communication with the Google secure server, so I had to force the internet connection to use TLS1.2
Finally I agree with Matt Barlow, it is an awesome package and saved me so much time.
Thanks for your reply Rob, I'm sure your code will be of help. I'm sorry, this issue has not been a priority here as we have been busy with a million other things, but I see it's been far too long now since it was first reported. I will try my best to make some time for this next week to fix the Recaptcha on multi-page forms using the input provided here.
I fixed this by moving the logic from the Event into the Recaptcha V2 field type and override the ValidateField method.
This means that you could validate per step in the form if you want too, as the validation is called between pages. So in PerplexRecaptcha.cs I added:
public override IEnumerable<string> ValidateField(Form form, Field field, IEnumerable<object> postedValues, HttpContextBase context)
{
var secretKey = Umbraco.Forms.Core.Configuration.GetSetting("RecaptchaPrivateKey");
if (string.IsNullOrEmpty(secretKey))
{
// just return the error message
LogHelper.Warn<UmbracoEvents>("ERROR: ReCaptcha v.2 is missing the Secret Key - Please update the '/app_plugins/umbracoforms/umbracoforms.config' to include 'key=\"RecaptchaPrivateKey\"'");
return new[] {ErrorMessage};
}
var reCaptchaResponse = context.Request["g-recaptcha-response"];
var url = $"https://www.google.com/recaptcha/api/siteverify?secret={secretKey}&response={reCaptchaResponse}";
var isSuccess = false;
var errorCodes = new List<string>();
using (var client = new WebClient())
{
var response = client.DownloadString(url);
var responseParsed = JObject.Parse(response);
//Get Success Status
JToken sucessToken;
var sucessFound = responseParsed.TryGetValue("success", out sucessToken);
if (sucessFound)
{
isSuccess = sucessToken.Value<bool>();
}
//Get Error codes
JToken errorsToken;
var errorsFound = responseParsed.TryGetValue("error-codes", out errorsToken);
if (errorsFound)
{
var errorsChildren = errorsToken.Children();
errorCodes.AddRange(errorsChildren.Select(child => child.Value<string>()));
}
else
{
errorCodes.Add("unknown-error");
}
}
if (isSuccess)
{
return Enumerable.Empty<string>();
}
return new[] { ErrorMessage };
}
I just uploaded a new version (1.8) which incorporates Matt's fix for this issue. ValidateField indeed seems the way to go for this one, thanks for your input!
Works OK on page 1 - when applied to the last page, I get the following error when I try to progress form Page 1
The operation has timed out
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
The operation has timed out
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Net.WebException: The operation has timed out
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[WebException: The operation has timed out]
System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request) +365
System.Net.WebClient.DownloadString(Uri address) +111
PerplexUmbraco.Forms.Code.UmbracoEvents.RecaptchaValidate(Object sender, FormValidationEventArgs e) +572
Umbraco.Forms.Web.Controllers.UmbracoFormsController.ValidateFormState(FormViewModel model, Form form, HttpContextBase context, Boolean captchaIsValid) +1719
Umbraco.Forms.Web.Controllers.UmbracoFormsController.HandleForm(FormViewModel model, Boolean captchaIsValid) +348
lambda_method(Closure , ControllerBase , Object[] ) +195
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +209
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +35
System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) +39
System.Web.Mvc.Async.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) +67
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +42
System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() +72
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +385
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +385
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +385
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +385
System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +385
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +42
System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +30
System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +185
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +38
System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +29
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +65
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +52
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +36
System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +38
System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +43
System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +65
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +38
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +393
System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +195
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +128
Did it also work on page 1 when you navigated or submitted the form? I'm asking because it seems the Network Request to Google timed out (according to the exception), so I wonder if your PC could simply (temporarily) not reach the Google server for the validation to occur. If that was indeed the case the Recaptcha should never actually work. It would render, but the validation would always throw this exception.
And do I understand correctly you get this error when the Recaptcha is on the last page (let's say page 3), and you navigate from page 1 to 2? I would imagine the Recaptcha validation should not trigger at all at the point. It should only trigger when submitting the whole form and/or when you navigate from page 3 to some other page (perhaps only when going forward but not sure). Our package does not control when validation occurs, as you can see in the code of Matt above we simply implement a ValidateField method to do the validation but this is called by Umbraco.
RecaptchaV2 on forms with multiple pages - can't navigate between form pages.
Hi,
This is an awesome package, we have a slight issue when implementing the RecaptchaV2 on forms that have multiple pages, adding the Recaptcha prevents navigation between pages of the form.
Have you spotted this before, any ideas how we can solve this?
Thanks,
Matt
Hi Matt,
I do not have much experience with multi page Umbraco Forms and have not seen this before. Does this happen with any fields that are mandatory and left empty when trying to navigate between pages? What about when using the default Recaptcha that comes with Forms? Does it still occur when the Recaptcha is checked or only when it's left empty?
Regards, Daniël
Hi Daniel,
Thanks for the reply, it's not to do with mandatory fields. I stepped through the source.
In UmbracoEvents.cs there is:
This adds the recaptcha check each time the form is validated, so when moving from one page to the next it fires. Then since the Recaptcha is on the 2nd page, the validation fails, and the navigation fails.
I wonder how Umbraco.Forms does this with their version of the Recaptcha?, as this does allow navigation between pages.
Probably need to update the code, so it only fires to validate if the page that is being validated contains a recaptcha control.
Looks like this can be done via the FormValidationEventArgs.
I had exactly the same problem with the reCAPTCHA validating on a multi-step form. Fortunately, the contourPage div sits within a form, which has a FormStep hidden field, the value of which is the current form step.
As all our forms (and I guess most others) have the reCAPTCHA right at the end of the form, it can safely be assumed (as in the code below) that the control is on the last step.
So, all you need to do is check to see if the current step is equal to the total number of steps, THEN do the reCAPTCHA validation, giving the following source change in UmbracoEvents.cs
You will notice one additional line which may come in handy. We disabled TLS1.1 on our server which stopped communication with the Google secure server, so I had to force the internet connection to use TLS1.2
Finally I agree with Matt Barlow, it is an awesome package and saved me so much time.
Thanks for your reply Rob, I'm sure your code will be of help. I'm sorry, this issue has not been a priority here as we have been busy with a million other things, but I see it's been far too long now since it was first reported. I will try my best to make some time for this next week to fix the Recaptcha on multi-page forms using the input provided here.
I fixed this by moving the logic from the Event into the Recaptcha V2 field type and override the ValidateField method.
This means that you could validate per step in the form if you want too, as the validation is called between pages. So in PerplexRecaptcha.cs I added:
and I removed from /code/UmbracoEvents.cs
and also removed the associated method.
Hi Rob & Matt -- and others :-),
I just uploaded a new version (1.8) which incorporates Matt's fix for this issue. ValidateField indeed seems the way to go for this one, thanks for your input!
-- Daniël
I am having this issue on a multi-page form:
Umbraco Forms 6.0.6
Umbraco version 7.7.8 assembly: 1.0.6582.14881
Perplex 1.8.2
Works OK on page 1 - when applied to the last page, I get the following error when I try to progress form Page 1
Hi ECL,
Did it also work on page 1 when you navigated or submitted the form? I'm asking because it seems the Network Request to Google timed out (according to the exception), so I wonder if your PC could simply (temporarily) not reach the Google server for the validation to occur. If that was indeed the case the Recaptcha should never actually work. It would render, but the validation would always throw this exception.
And do I understand correctly you get this error when the Recaptcha is on the last page (let's say page 3), and you navigate from page 1 to 2? I would imagine the Recaptcha validation should not trigger at all at the point. It should only trigger when submitting the whole form and/or when you navigate from page 3 to some other page (perhaps only when going forward but not sure). Our package does not control when validation occurs, as you can see in the code of Matt above we simply implement a
ValidateField
method to do the validation but this is called by Umbraco.Regards, Daniël
Awesome Daniel. thanks. :)
is working on a reply...