Browse Source

Moved captcha from the app layer to web

pull/15039/head
malik masis 4 years ago
parent
commit
fc6dae3c9e
  1. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj
  2. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs
  3. 66
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs
  4. 32
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs
  5. 7
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs
  6. 73
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs
  7. 175
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs
  8. 3
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj

2
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo.CmsKit.Public.Application.csproj

@ -9,8 +9,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13" />
<ProjectReference Include="..\Volo.CmsKit.Common.Application\Volo.CmsKit.Common.Application.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
</ItemGroup>

15
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs

@ -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)
{
}
}

66
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOptions.cs

@ -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;
}
}

32
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaOutput.cs

@ -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()
};
}
}

7
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/EncoderTypes.cs

@ -0,0 +1,7 @@
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public enum EncoderTypes
{
Jpeg,
Png,
}

73
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/RandomTextGenerator.cs

@ -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;
}
}

175
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs

@ -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);
}
}

3
modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj

@ -15,10 +15,11 @@
<ItemGroup>
<ProjectReference Include="..\Volo.CmsKit.Common.Web\Volo.CmsKit.Common.Web.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.CmsKit.Public.Application\Volo.CmsKit.Public.Application.csproj" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Markdig.Signed" Version="0.26.0" />
<PackageReference Include="HtmlSanitizer" Version="5.0.331" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13" />
</ItemGroup>

Loading…
Cancel
Save