mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
#5331: Updated the ReCaptcha API to the latest version (2.0)
Work Item: 5331
This commit is contained in:
@@ -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();
|
|
||||||
|
|
||||||
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) {
|
|
||||||
var context = workContext.HttpContext;
|
var context = workContext.HttpContext;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var result = ExecuteValidateRequest(
|
var result = ExecuteValidateRequest(
|
||||||
settings.PrivateKey,
|
settings.PrivateKey,
|
||||||
context.Request.ServerVariables["REMOTE_ADDR"],
|
context.Request.ServerVariables["REMOTE_ADDR"],
|
||||||
submitViewModel.recaptcha_challenge_field,
|
context.Request.Form["g-recaptcha-response"]
|
||||||
submitViewModel.recaptcha_response_field
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!HandleValidateResponse(context, result)) {
|
ReCaptchaPartResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaPartResponseModel>(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"));
|
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");
|
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"));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
|
||||||
|
@{
|
||||||
|
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>
|
<noscript>
|
||||||
<iframe src="//www.google.com/recaptcha/api/noscript?k=@Model.PublicKey" height="300" width="500" frameborder="0"></iframe><br>
|
<div style="width: 302px; height: 352px;">
|
||||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
|
<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>
|
</textarea>
|
||||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
</fieldset>
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
if (!responseModel.success) {
|
||||||
|
for (int i = 0; i < responseModel.errorMessage.Length; i++) {
|
||||||
|
if (responseModel.errorMessage[i] == "missing-input-response") {
|
||||||
var validationSettings = element.ValidationSettings;
|
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.");
|
var validationMessage = validationSettings.CustomValidationMessage.WithDefault("The Captcha field is required");
|
||||||
context.ModelState.AddModelError("recaptcha_response_field", T(validationMessage).Text);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div class="g-recaptcha" data-sitekey="@publicKey"></div>
|
||||||
<noscript>
|
<noscript>
|
||||||
<iframe src="//www.google.com/recaptcha/api/noscript?k=@publicKey" height="280" width="500" frameborder="0"></iframe><br>
|
<div style="width: 302px; height: 352px;">
|
||||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
|
<div style="width: 302px; height: 352px; position: relative;">
|
||||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
|
<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>
|
</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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user