mirror of https://github.com/abpframework/abp.git
committed by
GitHub
35 changed files with 787 additions and 98 deletions
@ -1,5 +1,4 @@ |
|||
using JetBrains.Annotations; |
|||
using System; |
|||
using Volo.Abp; |
|||
|
|||
namespace Volo.CmsKit.Comments; |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
using Volo.CmsKit.Comments; |
|||
|
|||
namespace Volo.CmsKit.Public.Comments; |
|||
|
|||
[Serializable] |
|||
public class CreateCommentWithParameteresInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] |
|||
public string Text { get; set; } |
|||
|
|||
[Required] |
|||
public string EntityType { get; set; } |
|||
|
|||
[Required] |
|||
public string EntityId { get; set; } |
|||
|
|||
public Guid? RepliedCommentId { get; set; } |
|||
|
|||
public Guid? CaptchaToken { get; set; } |
|||
|
|||
public int CaptchaAnswer { get; set; } |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.CmsKit.Comments; |
|||
using Volo.CmsKit.Public.Comments; |
|||
using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Commenting; |
|||
using Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Controllers; |
|||
|
|||
//[Route("cms-kit/public-comments")]
|
|||
public class CmsKitPublicCommentsController : AbpController |
|||
{ |
|||
public ICommentPublicAppService CommentPublicAppService { get; } |
|||
protected CmsKitCommentOptions CmsKitCommentOptions { get; } |
|||
public SimpleMathsCaptchaGenerator SimpleMathsCaptchaGenerator { get; } |
|||
|
|||
public CmsKitPublicCommentsController( |
|||
ICommentPublicAppService commentPublicAppService, |
|||
IOptions<CmsKitCommentOptions> cmsKitCommentOptions, |
|||
SimpleMathsCaptchaGenerator simpleMathsCaptchaGenerator) |
|||
{ |
|||
CommentPublicAppService = commentPublicAppService; |
|||
CmsKitCommentOptions = cmsKitCommentOptions.Value; |
|||
SimpleMathsCaptchaGenerator = simpleMathsCaptchaGenerator; |
|||
} |
|||
|
|||
[HttpPost] |
|||
public async Task ValidateAsync([FromBody] CreateCommentWithParameteresInput input) |
|||
{ |
|||
if (CmsKitCommentOptions.IsRecaptchaEnabled && input.CaptchaToken.HasValue) |
|||
{ |
|||
SimpleMathsCaptchaGenerator.Validate(input.CaptchaToken.Value, input.CaptchaAnswer); |
|||
} |
|||
|
|||
var dto = ObjectMapper.Map<CreateCommentWithParameteresInput, CreateCommentInput> (input); |
|||
await CommentPublicAppService.CreateAsync(input.EntityType, input.EntityId, dto); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
body { |
|||
.volo-captcha { |
|||
img { |
|||
border: 1px outset #dddcdc; |
|||
border-radius: 4px; |
|||
height: 45px; |
|||
} |
|||
|
|||
.d-flex { |
|||
.form-group { |
|||
width: 100%; |
|||
} |
|||
} |
|||
|
|||
@media (max-width: 800px) { |
|||
#Input_Captcha { |
|||
margin-left: 0px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.form-control { |
|||
border: 1px outset #e3e3e3 !important; |
|||
padding: 10px !important; |
|||
} |
|||
|
|||
#submit-button { |
|||
margin-top: 10px; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
$(function () { |
|||
|
|||
let l = abp.localization.getResource("CmsKit"); |
|||
|
|||
$('#deleteBlogPost').on('click', '', function (e) { |
|||
abp.message.confirm(l("DeleteBlogPostMessage"), function (ok) { |
|||
if (ok) { |
|||
volo.cmsKit.public.blogs.blogPostPublic.delete( |
|||
$('#BlogId').val() |
|||
).then(function () { |
|||
document.location.href = "/"; |
|||
}); |
|||
} |
|||
}) |
|||
}); |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
using System.Runtime.Serialization; |
|||
using Volo.Abp; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
public class CaptchaException : UserFriendlyException |
|||
{ |
|||
public CaptchaException(string message) : base(message) |
|||
{ |
|||
} |
|||
|
|||
public CaptchaException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Formats; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
public class CaptchaOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Default fonts are "Arial", "Verdana", "Times New Roman" in Windows. These fonts must exist in the target OS.
|
|||
/// </summary>
|
|||
public string[] FontFamilies { get; set; } = new string[] { "Arial", "Verdana", "Times New Roman" }; |
|||
|
|||
public Color[] TextColor { get; set; } = new Color[] |
|||
{ |
|||
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green |
|||
}; |
|||
public Color[] DrawLinesColor { get; set; } = new Color[] |
|||
{ |
|||
Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green |
|||
}; |
|||
|
|||
public float MinLineThickness { get; set; } = 0.7f; |
|||
|
|||
public float MaxLineThickness { get; set; } = 2.0f; |
|||
|
|||
public ushort Width { get; set; } = 180; |
|||
|
|||
public ushort Height { get; set; } = 70; |
|||
|
|||
public ushort NoiseRate { get; set; } = 500; |
|||
|
|||
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray }; |
|||
|
|||
public byte FontSize { get; set; } = 32; |
|||
|
|||
public FontStyle FontStyle { get; set; } = FontStyle.Regular; |
|||
|
|||
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Png; |
|||
|
|||
public IImageEncoder Encoder => RandomTextGenerator.GetEncoder(EncoderType); |
|||
|
|||
public byte DrawLines { get; set; } = 2; |
|||
|
|||
public byte MaxRotationDegrees { get; set; } = 4; |
|||
|
|||
public int Number1MinValue { get; set; } = 1; |
|||
|
|||
public int Number1MaxValue { get; set; } = 99; |
|||
|
|||
public int Number2MinValue { get; set; } = 1; |
|||
|
|||
public int Number2MaxValue { get; set; } = 99; |
|||
|
|||
public CaptchaOptions() |
|||
{ |
|||
|
|||
} |
|||
public CaptchaOptions(int number1MinValue, int number1MaxValue, int number2MinValue, int number2MaxValue) |
|||
{ |
|||
Number1MinValue = number1MinValue; |
|||
Number1MaxValue = number1MaxValue; |
|||
Number2MinValue = number2MinValue; |
|||
Number1MaxValue = number2MaxValue; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
public class CaptchaOutput |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public string Text { get; set; } |
|||
public byte[] ImageBytes { get; set; } |
|||
public int Result { get; set; } |
|||
} |
|||
|
|||
public class CaptchaInput |
|||
{ |
|||
public int Number1 { get; set; } |
|||
public int Number2 { get; set; } |
|||
} |
|||
|
|||
public class CaptchaRequest |
|||
{ |
|||
public CaptchaInput Input { get; set; } |
|||
public CaptchaOutput Output { get; set; } |
|||
|
|||
public CaptchaRequest() |
|||
{ |
|||
Input = new CaptchaInput(); |
|||
Output = new CaptchaOutput |
|||
{ |
|||
Id = Guid.NewGuid() |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
public enum EncoderTypes |
|||
{ |
|||
Jpeg, |
|||
Png, |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
using System; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
public static class RandomTextGenerator |
|||
{ |
|||
private static readonly char[] AllowedChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVXYZW23456789".ToCharArray(); |
|||
|
|||
public static IImageEncoder GetEncoder(EncoderTypes encoderType) |
|||
{ |
|||
IImageEncoder encoder = encoderType switch |
|||
{ |
|||
EncoderTypes.Png => new PngEncoder(), |
|||
EncoderTypes.Jpeg => new JpegEncoder(), |
|||
_ => throw new ArgumentException($"Encoder '{encoderType}' not found!") |
|||
}; |
|||
|
|||
return encoder; |
|||
} |
|||
|
|||
public static string GetRandomText(int size) |
|||
{ |
|||
var data = new byte[4 * size]; |
|||
using (var crypto = new RNGCryptoServiceProvider()) |
|||
{ |
|||
crypto.GetBytes(data); |
|||
} |
|||
|
|||
var result = new StringBuilder(size); |
|||
for (var i = 0; i < size; i++) |
|||
{ |
|||
var rnd = BitConverter.ToUInt32(data, i * 4); |
|||
var idx = rnd % AllowedChars.Length; |
|||
result.Append(AllowedChars[idx]); |
|||
} |
|||
|
|||
return result.ToString(); |
|||
} |
|||
|
|||
public static string GetUniqueKey(int size, char[] chars) |
|||
{ |
|||
var data = new byte[4 * size]; |
|||
using (var crypto = new RNGCryptoServiceProvider()) |
|||
{ |
|||
crypto.GetBytes(data); |
|||
} |
|||
|
|||
var result = new StringBuilder(size); |
|||
|
|||
for (var i = 0; i < size; i++) |
|||
{ |
|||
var rnd = BitConverter.ToUInt32(data, i * 4); |
|||
var idx = rnd % chars.Length; |
|||
result.Append(chars[idx]); |
|||
} |
|||
|
|||
return result.ToString(); |
|||
} |
|||
|
|||
public static float GenerateNextFloat(double min = -3.40282347E+38, double max = 3.40282347E+38) |
|||
{ |
|||
var random = new Random(); |
|||
var range = max - min; |
|||
var sample = random.NextDouble(); |
|||
var scaled = sample * range + min; |
|||
var result = (float)scaled; |
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Drawing.Processing; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Color = SixLabors.ImageSharp.Color; |
|||
using PointF = SixLabors.ImageSharp.PointF; |
|||
|
|||
namespace Volo.CmsKit.Public.Web.Security.Captcha; |
|||
|
|||
public class SimpleMathsCaptchaGenerator : ISingletonDependency |
|||
{ |
|||
private static Dictionary<Guid, CaptchaRequest> Session { get; set; } = new Dictionary<Guid, CaptchaRequest>(); |
|||
|
|||
public CaptchaOutput Generate() |
|||
{ |
|||
return Generate(options: null, number1: null, number2: null); |
|||
} |
|||
|
|||
public CaptchaOutput Generate(CaptchaOptions options) |
|||
{ |
|||
return Generate(options, number1: null, number2: null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a simple captcha code.
|
|||
/// </summary>
|
|||
/// <param name="options">Options for captcha generation</param>
|
|||
/// <param name="number1">First number for maths operation</param>
|
|||
/// <param name="number2">Second number for maths operation</param>
|
|||
/// <returns></returns>
|
|||
public CaptchaOutput Generate(CaptchaOptions options, int? number1, int? number2) |
|||
{ |
|||
var random = new Random(); |
|||
options ??= new CaptchaOptions(); |
|||
|
|||
number1 ??= random.Next(options.Number1MinValue, options.Number1MaxValue); |
|||
number2 ??= random.Next(options.Number2MinValue, options.Number2MaxValue); |
|||
|
|||
var text = number1 + "+" + number2; |
|||
var request = new CaptchaRequest |
|||
{ |
|||
Input = |
|||
{ |
|||
Number1 = number1.Value, |
|||
Number2 = number2.Value |
|||
}, |
|||
Output = |
|||
{ |
|||
Text = text, |
|||
Result = Calculate(number1.Value, number2.Value), |
|||
ImageBytes = GenerateInternal(text, options) |
|||
} |
|||
}; |
|||
|
|||
Session[request.Output.Id] = request; |
|||
return request.Output; |
|||
} |
|||
|
|||
private static int Calculate(int number1, int number2) |
|||
{ |
|||
return number1 + number2; |
|||
} |
|||
|
|||
public void Validate(Guid requestId, int value) |
|||
{ |
|||
var request = Session[requestId]; |
|||
if (request.Output.Result != value) |
|||
{ |
|||
throw new CaptchaException("The captcha code doesn't match text on the picture! Please try again."); |
|||
} |
|||
} |
|||
|
|||
public void Validate(Guid requestId, string value) |
|||
{ |
|||
if (int.TryParse(value, out var captchaInput)) |
|||
{ |
|||
Validate(requestId, captchaInput); |
|||
} |
|||
else |
|||
{ |
|||
throw new CaptchaException("The captcha code is missing!"); |
|||
} |
|||
} |
|||
|
|||
private byte[] GenerateInternal(string stringText, CaptchaOptions options) |
|||
{ |
|||
byte[] result; |
|||
|
|||
using (var image = new Image<Rgba32>(options.Width, options.Height)) |
|||
{ |
|||
float position = 0; |
|||
var random = new Random(); |
|||
var startWith = (byte)random.Next(5, 10); |
|||
image.Mutate(ctx => ctx.BackgroundColor(Color.Transparent)); |
|||
var fontName = options.FontFamilies[random.Next(0, options.FontFamilies.Length)]; |
|||
var font = SystemFonts.CreateFont(fontName, options.FontSize, options.FontStyle); |
|||
|
|||
foreach (var character in stringText) |
|||
{ |
|||
var text = character.ToString(); |
|||
var color = options.TextColor[random.Next(0, options.TextColor.Length)]; |
|||
var location = new PointF(startWith + position, random.Next(6, 13)); |
|||
image.Mutate(ctx => ctx.DrawText(text, font, color, location)); |
|||
position += TextMeasurer.Measure(character.ToString(), new RendererOptions(font, location)).Width; |
|||
} |
|||
|
|||
//add rotation
|
|||
var rotation = GetRotation(options); |
|||
image.Mutate(ctx => ctx.Transform(rotation)); |
|||
|
|||
// add the dynamic image to original image
|
|||
var size = (ushort)TextMeasurer.Measure(stringText, new RendererOptions(font)).Width; |
|||
var img = new Image<Rgba32>(size + 15, options.Height); |
|||
img.Mutate(ctx => ctx.BackgroundColor(Color.White)); |
|||
|
|||
Parallel.For(0, options.DrawLines, i => |
|||
{ |
|||
var x0 = random.Next(0, random.Next(0, 30)); |
|||
var y0 = random.Next(10, img.Height); |
|||
|
|||
var x1 = random.Next(30, img.Width); |
|||
var y1 = random.Next(0, img.Height); |
|||
|
|||
img.Mutate(ctx => |
|||
ctx.DrawLines(options.TextColor[random.Next(0, options.TextColor.Length)], |
|||
RandomTextGenerator.GenerateNextFloat(options.MinLineThickness, options.MaxLineThickness), |
|||
new PointF[] { new PointF(x0, y0), new PointF(x1, y1) }) |
|||
); |
|||
}); |
|||
|
|||
img.Mutate(ctx => ctx.DrawImage(image, 0.80f)); |
|||
|
|||
Parallel.For(0, options.NoiseRate, i => |
|||
{ |
|||
var x0 = random.Next(0, img.Width); |
|||
var y0 = random.Next(0, img.Height); |
|||
img.Mutate( |
|||
ctx => ctx |
|||
.DrawLines(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)], |
|||
RandomTextGenerator.GenerateNextFloat(0.5, 1.5), new PointF[] { new Vector2(x0, y0), new Vector2(x0, y0) }) |
|||
); |
|||
}); |
|||
|
|||
img.Mutate(x => |
|||
{ |
|||
x.Resize(options.Width, options.Height); |
|||
}); |
|||
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
img.Save(ms, options.Encoder); |
|||
result = ms.ToArray(); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static AffineTransformBuilder GetRotation(CaptchaOptions options) |
|||
{ |
|||
var random = new Random(); |
|||
var width = random.Next(10, options.Width); |
|||
var height = random.Next(10, options.Height); |
|||
var pointF = new PointF(width, height); |
|||
var rotationDegrees = random.Next(0, options.MaxRotationDegrees); |
|||
return new AffineTransformBuilder().PrependRotationDegrees(rotationDegrees, pointF); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue