mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Merge pull request #5332 from emeraldarcher/issue/5331
Update Google Recaptcha API Fixes #5331
This commit is contained in:
@@ -12,18 +12,21 @@ using Orchard.Localization;
|
||||
using Orchard.Logging;
|
||||
using Orchard.UI.Admin;
|
||||
using Orchard.UI.Notify;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.AntiSpam.Drivers {
|
||||
public class ReCaptchaPartDriver : ContentPartDriver<ReCaptchaPart> {
|
||||
private readonly INotifier _notifier;
|
||||
private readonly IJsonConverter _jsonConverter;
|
||||
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,
|
||||
IJsonConverter jsonConverter,
|
||||
IWorkContextAccessor workContextAccessor) {
|
||||
_notifier = notifier;
|
||||
_jsonConverter = jsonConverter;
|
||||
_workContextAccessor = workContextAccessor;
|
||||
T = NullLocalizer.Instance;
|
||||
Logger = NullLogger.Instance;
|
||||
@@ -67,67 +70,55 @@ namespace Orchard.AntiSpam.Drivers {
|
||||
return null;
|
||||
}
|
||||
|
||||
var submitViewModel = new ReCaptchaPartSubmitViewModel();
|
||||
var context = workContext.HttpContext;
|
||||
|
||||
if(updater.TryUpdateModel(submitViewModel, String.Empty, null, null)) {
|
||||
var context = workContext.HttpContext;
|
||||
try {
|
||||
var result = ExecuteValidateRequest(
|
||||
settings.PrivateKey,
|
||||
context.Request.ServerVariables["REMOTE_ADDR"],
|
||||
context.Request.Form["g-recaptcha-response"]
|
||||
);
|
||||
|
||||
try {
|
||||
var result = ExecuteValidateRequest(
|
||||
settings.PrivateKey,
|
||||
context.Request.ServerVariables["REMOTE_ADDR"],
|
||||
submitViewModel.recaptcha_challenge_field,
|
||||
submitViewModel.recaptcha_response_field
|
||||
);
|
||||
ReCaptchaPartResponseModel responseModel = _jsonConverter.Deserialize<ReCaptchaPartResponseModel>(result);
|
||||
|
||||
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"));
|
||||
if (!responseModel.Success) {
|
||||
for (int i = 0; i < responseModel.ErrorCodes.Length; i++) {
|
||||
if (responseModel.ErrorCodes[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.Information("Error occurred while submitting a reCaptcha: " + responseModel.ErrorCodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
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"));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error(e, "An unexcepted error occurred 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\lib\newtonsoft.json\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations">
|
||||
@@ -124,6 +128,7 @@
|
||||
<Compile Include="Services\AkismetSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\ISpamEventHandler.cs" />
|
||||
<Compile Include="Services\MissingFilterBanner.cs" />
|
||||
<Compile Include="Services\MissingSettingsBanner.cs" />
|
||||
<Compile Include="Services\TypePadSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\DefaultSpamFilterProvider.cs" />
|
||||
<Compile Include="Services\NullSpamFilterProvider.cs" />
|
||||
|
||||
@@ -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,15 @@
|
||||
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 {
|
||||
[JsonProperty("success")]
|
||||
public string Success { get; set; }
|
||||
|
||||
[JsonProperty("error-codes")]
|
||||
public string[] ErrorCodes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,32 @@
|
||||
@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>
|
||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
|
||||
</textarea>
|
||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge">
|
||||
</noscript>
|
||||
|
||||
@{
|
||||
var publicKey = (string)Model.PublicKey;
|
||||
}
|
||||
@if (!HasText(publicKey)) {
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
|
||||
<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>
|
||||
}
|
||||
@@ -292,6 +292,7 @@
|
||||
<Compile Include="ViewModels\FieldBindingSettings.cs" />
|
||||
<Compile Include="ViewModels\PartBindingSettings.cs" />
|
||||
<Compile Include="ViewModels\BlueprintsIndexViewModel.cs" />
|
||||
<Compile Include="ViewModels\ReCaptchaElementViewModel.cs" />
|
||||
<Compile Include="ViewModels\SubmissionViewModel.cs" />
|
||||
<Compile Include="ViewModels\SubmissionsIndexViewModel.cs" />
|
||||
<Compile Include="Helpers\SubmissionExtensions.cs" />
|
||||
|
||||
@@ -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,65 +31,59 @@ 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)) {
|
||||
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);
|
||||
ReCaptchaElementResponseModel responseModel = Newtonsoft.Json.JsonConvert.DeserializeObject<ReCaptchaElementResponseModel>(result);
|
||||
|
||||
if (!responseModel.Success) {
|
||||
for (int i = 0; i < responseModel.ErrorCodes.Length; i++) {
|
||||
if (responseModel.ErrorCodes[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.Information("Error occurred while submitting a reCaptcha: " + responseModel.ErrorCodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error(e, "An unexcepted error occured while submitting a reCaptcha");
|
||||
Logger.Error(e, "An unexcepted error occurred while submitting a reCaptcha");
|
||||
context.ModelState.AddModelError("recaptcha_response_field", T("There was an error while validating the Captcha image.").Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Orchard.DynamicForms.ViewModels {
|
||||
public class ReCaptchaElementResponseModel {
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("error-codes")]
|
||||
public string[] ErrorCodes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,33 @@
|
||||
}
|
||||
@if (!String.IsNullOrWhiteSpace(publicKey)) {
|
||||
@tagBuilder.StartElement
|
||||
<script type="text/javascript" src="//www.google.com/recaptcha/api/challenge?k=@publicKey"></script>
|
||||
<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">
|
||||
</noscript>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
|
||||
<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>
|
||||
@tagBuilder.EndElement
|
||||
if (element.ValidationSettings.ShowValidationMessage == true) {
|
||||
@Html.ValidationMessage("recaptcha_response_field")
|
||||
@Html.ValidationMessage("g-recaptcha-response")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user