#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> {
private readonly INotifier _notifier;
private readonly IWorkContextAccessor _workContextAccessor;
private const string ReCaptchaUrl = "http://www.google.com/recaptcha/api";
private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api";
private const string ReCaptchaSecureUrl = "https://www.google.com/recaptcha/api/siteverify";
public ReCaptchaPartDriver(
INotifier notifier,
@@ -67,67 +66,55 @@ namespace Orchard.AntiSpam.Drivers {
return null;
}
var submitViewModel = new ReCaptchaPartSubmitViewModel();
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) {
var context = workContext.HttpContext;
try {
var result = ExecuteValidateRequest(
settings.PrivateKey,
context.Request.ServerVariables["REMOTE_ADDR"],
submitViewModel.recaptcha_challenge_field,
submitViewModel.recaptcha_response_field
context.Request.Form["g-recaptcha-response"]
);
if (!HandleValidateResponse(context, result)) {
_notifier.Error(T("The text you entered in the Captcha field does not match the image"));
updater.AddModelError("", T("The text you entered in the Captcha field does not match the image"));
ReCaptchaPartResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaPartResponseModel>(result);
if (!responseModel.success) {
for (int i = 0; i < responseModel.errorMessage.Length; i++) {
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) {
}
}
catch (Exception e) {
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);
}
private static string ExecuteValidateRequest(string privateKey, string remoteip, string challenge, string response) {
WebRequest request = WebRequest.Create(ReCaptchaUrl + "/verify");
request.Method = "POST";
private static string ExecuteValidateRequest(string privateKey, string remoteip, string response) {
var postData = String.Format(CultureInfo.InvariantCulture,
"secret={0}&response={1}&remoteip={2}",
privateKey,
response,
remoteip
);
WebRequest request = WebRequest.Create(ReCaptchaSecureUrl + "?" + postData);
request.Method = "GET";
request.Timeout = 5000; //milliseconds
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 (var reader = new StreamReader(webResponse.GetResponseStream())) {
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 string PublicKey { get; set; }
}
public class ReCaptchaPartSubmitViewModel {
public string recaptcha_challenge_field { get; set; }
public string recaptcha_response_field { get; set; }
public class ReCaptchaPartResponseModel {
public bool success { get; set; }
[JsonProperty("error-codes")]
public string[] errorMessage { get; set; }
}
}

View File

@@ -1,8 +1,32 @@
@model Orchard.AntiSpam.ViewModels.ReCaptchaPartEditViewModel
<script type="text/javascript" src="//www.google.com/recaptcha/api/challenge?k=@Model.PublicKey"></script>
@{
var publicKey = (string)Model.PublicKey;
}
@if (!String.IsNullOrWhiteSpace(publicKey)) {
<script src='https://www.google.com/recaptcha/api.js'></script>
<fieldset>
<div class="g-recaptcha" data-sitekey="@publicKey"></div>
<noscript>
<iframe src="//www.google.com/recaptcha/api/noscript?k=@Model.PublicKey" height="300" width="500" frameborder="0"></iframe><br>
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
<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>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
</div>
</div>
</div>
</noscript>
</fieldset>
}

View File

@@ -7,6 +7,7 @@ using System.Web;
using Orchard.AntiSpam.Models;
using Orchard.ContentManagement;
using Orchard.DynamicForms.Elements;
using Orchard.DynamicForms.ViewModels;
using Orchard.DynamicForms.Helpers;
using Orchard.DynamicForms.Services;
using Orchard.DynamicForms.Services.Models;
@@ -19,7 +20,7 @@ namespace Orchard.DynamicForms.Validators {
_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) {
var workContext = _workContextAccessor.GetContext();
@@ -30,22 +31,32 @@ namespace Orchard.DynamicForms.Validators {
}
var httpContext = workContext.HttpContext;
var response = context.Values["recaptcha_response_field"];
var challenge = context.Values["recaptcha_challenge_field"];
var response = context.Values["g-recaptcha-response"];
if (context.ModelState.IsValid) {
try {
var result = ExecuteValidateRequest(
settings.PrivateKey,
httpContext.Request.ServerVariables["REMOTE_ADDR"],
challenge,
response
);
if (!HandleValidateResponse(httpContext, result)) {
ReCaptchaElementResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaElementResponseModel>(result);
if (!responseModel.success) {
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 text you entered in the Captcha field does not match the image. Please try again.");
context.ModelState.AddModelError("recaptcha_response_field", T(validationMessage).Text);
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) {
@@ -55,40 +66,24 @@ namespace Orchard.DynamicForms.Validators {
}
}
private static string ExecuteValidateRequest(string privateKey, string remoteip, string challenge, string response) {
var request = WebRequest.Create(ReCaptchaUrl + "/verify");
request.Method = "POST";
private static string ExecuteValidateRequest(string privateKey, string remoteip, string response) {
var postData = String.Format(CultureInfo.InvariantCulture,
"secret={0}&response={1}&remoteip={2}",
privateKey,
response,
remoteip
);
WebRequest request = WebRequest.Create(ReCaptchaSecureUrl + "?" + postData);
request.Method = "GET";
request.Timeout = 5000; //milliseconds
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
);
var content = Encoding.UTF8.GetBytes(postData);
using (var stream = request.GetRequestStream()) {
stream.Write(content, 0, content.Length);
}
using (var webResponse = request.GetResponse()) {
using (WebResponse webResponse = request.GetResponse()) {
using (var reader = new StreamReader(webResponse.GetResponseStream())) {
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)) {
@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>
<fieldset>
<div class="g-recaptcha" data-sitekey="@publicKey"></div>
<noscript>
<iframe src="//www.google.com/recaptcha/api/noscript?k=@publicKey" height="280" 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">
<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
if (element.ValidationSettings.ShowValidationMessage == true) {
@Html.ValidationMessage("recaptcha_response_field")
@Html.ValidationMessage("g-recaptcha-response")
}
}