#5331: Updated the ReCaptcha API to the latest version (2.0)

Work Item: 5331
This commit is contained in:
EmeraldArcher
2015-05-28 14:58:18 -06:00
parent 1e83068b8c
commit 77b4214113
7 changed files with 170 additions and 99 deletions

View File

@@ -17,8 +17,7 @@ namespace Orchard.AntiSpam.Drivers {
public class ReCaptchaPartDriver : ContentPartDriver<ReCaptchaPart> { public class ReCaptchaPartDriver : ContentPartDriver<ReCaptchaPart> {
private readonly INotifier _notifier; private readonly INotifier _notifier;
private readonly IWorkContextAccessor _workContextAccessor; private readonly IWorkContextAccessor _workContextAccessor;
private const string ReCaptchaUrl = "http://www.google.com/recaptcha/api"; private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api/siteverify";
private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api";
public ReCaptchaPartDriver( public ReCaptchaPartDriver(
INotifier notifier, INotifier notifier,
@@ -67,67 +66,55 @@ namespace Orchard.AntiSpam.Drivers {
return null; return null;
} }
var submitViewModel = new ReCaptchaPartSubmitViewModel(); var context = workContext.HttpContext;
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) { try {
var context = workContext.HttpContext; var result = ExecuteValidateRequest(
settings.PrivateKey,
context.Request.ServerVariables["REMOTE_ADDR"],
context.Request.Form["g-recaptcha-response"]
);
try { ReCaptchaPartResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaPartResponseModel>(result);
var result = ExecuteValidateRequest(
settings.PrivateKey,
context.Request.ServerVariables["REMOTE_ADDR"],
submitViewModel.recaptcha_challenge_field,
submitViewModel.recaptcha_response_field
);
if (!HandleValidateResponse(context, result)) { if (!responseModel.success) {
_notifier.Error(T("The text you entered in the Captcha field does not match the image")); for (int i = 0; i < responseModel.errorMessage.Length; i++) {
updater.AddModelError("", T("The text you entered in the Captcha field does not match the image")); if (responseModel.errorMessage[i] == "missing-input-response") {
_notifier.Error(T("The Captcha field is required"));
}
else {
_notifier.Error(T("There was an error with the Captcha please try again"));
Logger.Error("Error occured while submitting a reCaptcha: " + responseModel.errorMessage[i]);
}
} }
} }
catch(Exception e) { }
Logger.Error(e, "An unexcepted error occured while submitting a reCaptcha"); catch (Exception e) {
updater.AddModelError("Parts_ReCaptcha_Fields", T("There was an error while validating the Captcha image")); Logger.Error(e, "An unexcepted error occured while submitting a reCaptcha");
} updater.AddModelError("Parts_ReCaptcha_Fields", T("There was an error while validating the Captcha image"));
} }
return Editor(part, shapeHelper); return Editor(part, shapeHelper);
} }
private static string ExecuteValidateRequest(string privateKey, string remoteip, string challenge, string response) { private static string ExecuteValidateRequest(string privateKey, string remoteip, string response) {
WebRequest request = WebRequest.Create(ReCaptchaUrl + "/verify"); var postData = String.Format(CultureInfo.InvariantCulture,
request.Method = "POST"; "secret={0}&response={1}&remoteip={2}",
privateKey,
response,
remoteip
);
WebRequest request = WebRequest.Create(ReCaptchaSecureUrl + "?" + postData);
request.Method = "GET";
request.Timeout = 5000; //milliseconds request.Timeout = 5000; //milliseconds
request.ContentType = "application/x-www-form-urlencoded"; request.ContentType = "application/x-www-form-urlencoded";
var postData = String.Format(CultureInfo.InvariantCulture,
"privatekey={0}&remoteip={1}&challenge={2}&response={3}",
privateKey,
remoteip,
challenge,
response
);
byte[] content = Encoding.UTF8.GetBytes(postData);
using (Stream stream = request.GetRequestStream()) {
stream.Write(content, 0, content.Length);
}
using (WebResponse webResponse = request.GetResponse()) { using (WebResponse webResponse = request.GetResponse()) {
using (var reader = new StreamReader(webResponse.GetResponseStream())) { using (var reader = new StreamReader(webResponse.GetResponseStream())) {
return reader.ReadToEnd(); return reader.ReadToEnd();
} }
} }
} }
internal static bool HandleValidateResponse(HttpContextBase context, string response) {
if (!String.IsNullOrEmpty(response)) {
string[] results = response.Split('\n');
if (results.Length > 0) {
bool rval = Convert.ToBoolean(results[0], CultureInfo.InvariantCulture);
return rval;
}
}
return false;
}
} }
} }

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using Orchard.AntiSpam.Models;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.UI.Admin.Notification;
using Orchard.UI.Notify;
using System.Web.Mvc;
namespace Orchard.AntiSpam.Services {
public class MissingSettingsBanner : INotificationProvider {
private readonly IOrchardServices _orchardServices;
public MissingSettingsBanner(IOrchardServices orchardServices) {
_orchardServices = orchardServices;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public IEnumerable<NotifyEntry> GetNotifications() {
var workContext = _orchardServices.WorkContext;
var RecaptchaSettings = workContext.CurrentSite.As<ReCaptchaSettingsPart>();
if (RecaptchaSettings.PublicKey == null || RecaptchaSettings.PrivateKey == null) {
var urlHelper = new UrlHelper(workContext.HttpContext.Request.RequestContext);
var url = urlHelper.Action("Spam", "Admin", new { Area = "Settings" });
yield return new NotifyEntry { Message = T("The <a href=\"{0}\">ReCaptcha settings</a> need to be configured.", url), Type = NotifyType.Warning };
}
}
}
}

View File

@@ -1,10 +1,14 @@
namespace Orchard.AntiSpam.ViewModels { using Newtonsoft.Json;
namespace Orchard.AntiSpam.ViewModels {
public class ReCaptchaPartEditViewModel { public class ReCaptchaPartEditViewModel {
public string PublicKey { get; set; } public string PublicKey { get; set; }
} }
public class ReCaptchaPartSubmitViewModel { public class ReCaptchaPartResponseModel {
public string recaptcha_challenge_field { get; set; } public bool success { get; set; }
public string recaptcha_response_field { get; set; }
[JsonProperty("error-codes")]
public string[] errorMessage { get; set; }
} }
} }

View File

@@ -1,8 +1,32 @@
@model Orchard.AntiSpam.ViewModels.ReCaptchaPartEditViewModel @model Orchard.AntiSpam.ViewModels.ReCaptchaPartEditViewModel
<script type="text/javascript" src="//www.google.com/recaptcha/api/challenge?k=@Model.PublicKey"></script>
<noscript> @{
<iframe src="//www.google.com/recaptcha/api/noscript?k=@Model.PublicKey" height="300" width="500" frameborder="0"></iframe><br> var publicKey = (string)Model.PublicKey;
<textarea name="recaptcha_challenge_field" rows="3" cols="40"> }
</textarea> @if (!String.IsNullOrWhiteSpace(publicKey)) {
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"> <script src='https://www.google.com/recaptcha/api.js'></script>
</noscript>
<fieldset>
<div class="g-recaptcha" data-sitekey="@publicKey"></div>
<noscript>
<div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.google.com/recaptcha/api/fallback?k=@publicKey"
frameborder="0" scrolling="no"
style="width: 302px; height:352px; border-style: none;">
</iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 80px; min-height: 80px; border: 1px solid #c1c1c1;
margin: 0px; padding: 0px; resize: none;" value="">
</textarea>
</div>
</div>
</div>
</noscript>
</fieldset>
}

View File

@@ -7,6 +7,7 @@ using System.Web;
using Orchard.AntiSpam.Models; using Orchard.AntiSpam.Models;
using Orchard.ContentManagement; using Orchard.ContentManagement;
using Orchard.DynamicForms.Elements; using Orchard.DynamicForms.Elements;
using Orchard.DynamicForms.ViewModels;
using Orchard.DynamicForms.Helpers; using Orchard.DynamicForms.Helpers;
using Orchard.DynamicForms.Services; using Orchard.DynamicForms.Services;
using Orchard.DynamicForms.Services.Models; using Orchard.DynamicForms.Services.Models;
@@ -19,7 +20,7 @@ namespace Orchard.DynamicForms.Validators {
_workContextAccessor = workContextAccessor; _workContextAccessor = workContextAccessor;
} }
private const string ReCaptchaUrl = "http://www.google.com/recaptcha/api"; private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api/siteverify";
protected override void OnValidate(ReCaptcha element, ValidateInputContext context) { protected override void OnValidate(ReCaptcha element, ValidateInputContext context) {
var workContext = _workContextAccessor.GetContext(); var workContext = _workContextAccessor.GetContext();
@@ -30,22 +31,32 @@ namespace Orchard.DynamicForms.Validators {
} }
var httpContext = workContext.HttpContext; var httpContext = workContext.HttpContext;
var response = context.Values["recaptcha_response_field"]; var response = context.Values["g-recaptcha-response"];
var challenge = context.Values["recaptcha_challenge_field"];
if (context.ModelState.IsValid) { if (context.ModelState.IsValid) {
try { try {
var result = ExecuteValidateRequest( var result = ExecuteValidateRequest(
settings.PrivateKey, settings.PrivateKey,
httpContext.Request.ServerVariables["REMOTE_ADDR"], httpContext.Request.ServerVariables["REMOTE_ADDR"],
challenge,
response response
); );
if (!HandleValidateResponse(httpContext, result)) { ReCaptchaElementResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaElementResponseModel>(result);
var validationSettings = element.ValidationSettings;
var validationMessage = validationSettings.CustomValidationMessage.WithDefault("The text you entered in the Captcha field does not match the image. Please try again."); if (!responseModel.success) {
context.ModelState.AddModelError("recaptcha_response_field", T(validationMessage).Text); for (int i = 0; i < responseModel.errorMessage.Length; i++) {
if (responseModel.errorMessage[i] == "missing-input-response") {
var validationSettings = element.ValidationSettings;
var validationMessage = validationSettings.CustomValidationMessage.WithDefault("The Captcha field is required");
context.ModelState.AddModelError("g-recaptcha-response", T(validationMessage).Text);
}
else {
var validationSettings = element.ValidationSettings;
var validationMessage = validationSettings.CustomValidationMessage.WithDefault("There was an error with the Captcha please try again");
context.ModelState.AddModelError("g-recaptcha-response", T(validationMessage).Text);
Logger.Error("Error occured while submitting a reCaptcha: " + responseModel.errorMessage[i]);
}
}
} }
} }
catch (Exception e) { catch (Exception e) {
@@ -55,40 +66,24 @@ namespace Orchard.DynamicForms.Validators {
} }
} }
private static string ExecuteValidateRequest(string privateKey, string remoteip, string challenge, string response) { private static string ExecuteValidateRequest(string privateKey, string remoteip, string response) {
var request = WebRequest.Create(ReCaptchaUrl + "/verify"); var postData = String.Format(CultureInfo.InvariantCulture,
request.Method = "POST"; "secret={0}&response={1}&remoteip={2}",
privateKey,
response,
remoteip
);
WebRequest request = WebRequest.Create(ReCaptchaSecureUrl + "?" + postData);
request.Method = "GET";
request.Timeout = 5000; //milliseconds request.Timeout = 5000; //milliseconds
request.ContentType = "application/x-www-form-urlencoded"; request.ContentType = "application/x-www-form-urlencoded";
var postData = String.Format(CultureInfo.InvariantCulture, using (WebResponse webResponse = request.GetResponse()) {
"privatekey={0}&remoteip={1}&challenge={2}&response={3}",
privateKey,
remoteip,
challenge,
response
);
var content = Encoding.UTF8.GetBytes(postData);
using (var stream = request.GetRequestStream()) {
stream.Write(content, 0, content.Length);
}
using (var webResponse = request.GetResponse()) {
using (var reader = new StreamReader(webResponse.GetResponseStream())) { using (var reader = new StreamReader(webResponse.GetResponseStream())) {
return reader.ReadToEnd(); return reader.ReadToEnd();
} }
} }
} }
internal static bool HandleValidateResponse(HttpContextBase context, string response) {
if (!String.IsNullOrEmpty(response)) {
var results = response.Split('\n');
if (results.Length > 0) {
var rval = Convert.ToBoolean(results[0], CultureInfo.InvariantCulture);
return rval;
}
}
return false;
}
} }
} }

View File

@@ -0,0 +1,11 @@
using Newtonsoft.Json;
namespace Orchard.DynamicForms.ViewModels
{
public class ReCaptchaElementResponseModel {
public bool success { get; set; }
[JsonProperty("error-codes")]
public string[] errorMessage { get; set; }
}
}

View File

@@ -7,14 +7,33 @@
} }
@if (!String.IsNullOrWhiteSpace(publicKey)) { @if (!String.IsNullOrWhiteSpace(publicKey)) {
@tagBuilder.StartElement @tagBuilder.StartElement
<script type="text/javascript" src="//www.google.com/recaptcha/api/challenge?k=@publicKey"></script> <script src='https://www.google.com/recaptcha/api.js'></script>
<noscript>
<iframe src="//www.google.com/recaptcha/api/noscript?k=@publicKey" height="280" width="500" frameborder="0"></iframe><br> <fieldset>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea> <div class="g-recaptcha" data-sitekey="@publicKey"></div>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"> <noscript>
</noscript> <div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.google.com/recaptcha/api/fallback?k=@publicKey"
frameborder="0" scrolling="no"
style="width: 302px; height:352px; border-style: none;">
</iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none;
bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response"
class="g-recaptcha-response"
style="width: 250px; height: 80px; min-height: 80px; border: 1px solid #c1c1c1;
margin: 0px; padding: 0px; resize: none;" value="">
</textarea>
</div>
</div>
</div>
</noscript>
</fieldset>
@tagBuilder.EndElement @tagBuilder.EndElement
if (element.ValidationSettings.ShowValidationMessage == true) { if (element.ValidationSettings.ShowValidationMessage == true) {
@Html.ValidationMessage("recaptcha_response_field") @Html.ValidationMessage("g-recaptcha-response")
} }
} }