Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
25
Vendor/EmbedIO-3.5.2/Security/BanInfo.cs
vendored
Normal file
25
Vendor/EmbedIO-3.5.2/Security/BanInfo.cs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Net;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the ban of an IP address.
|
||||
/// </summary>
|
||||
public class BanInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the banned IP address.
|
||||
/// </summary>
|
||||
public IPAddress IPAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the expiration time of the ban.
|
||||
/// </summary>
|
||||
public long ExpiresAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance was explicitly banned.
|
||||
/// </summary>
|
||||
public bool IsExplicit { get; set; }
|
||||
}
|
||||
}
|
||||
30
Vendor/EmbedIO-3.5.2/Security/IIPBanningCriterion.cs
vendored
Normal file
30
Vendor/EmbedIO-3.5.2/Security/IIPBanningCriterion.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a criterion for <see cref="IPBanningModule"/>.
|
||||
/// </summary>
|
||||
public interface IIPBanningCriterion : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates the IP address should be banned or not.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <returns><c>true</c> if the IP Address should be banned, otherwise <c>false</c>.</returns>
|
||||
Task<bool> ValidateIPAddress(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the data generated by an IP address.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
void ClearIPAddress(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Purges the data of the Criterion.
|
||||
/// </summary>
|
||||
void PurgeData();
|
||||
}
|
||||
}
|
||||
188
Vendor/EmbedIO-3.5.2/Security/IPBanningConfiguration.cs
vendored
Normal file
188
Vendor/EmbedIO-3.5.2/Security/IPBanningConfiguration.cs
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
using EmbedIO.Utilities;
|
||||
using Swan.Configuration;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a configuration object for <see cref="IPBanningModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ConfiguredObject" />
|
||||
public class IPBanningConfiguration : ConfiguredObject, IDisposable
|
||||
{
|
||||
private readonly List<IIPBanningCriterion> _criterions = new List<IIPBanningCriterion>();
|
||||
private readonly ConcurrentDictionary<IPAddress, BanInfo> _blacklistDictionary = new ConcurrentDictionary<IPAddress, BanInfo>();
|
||||
private readonly ConcurrentBag<IPAddress> _whiteListBag = new ConcurrentBag<IPAddress>();
|
||||
private readonly int _banTime;
|
||||
private bool _disposed;
|
||||
|
||||
internal IPBanningConfiguration(int banTime)
|
||||
{
|
||||
_banTime = banTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="IPBanningConfiguration"/> class.
|
||||
/// </summary>
|
||||
~IPBanningConfiguration()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the black list.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The black list.
|
||||
/// </value>
|
||||
public List<BanInfo> BlackList => _blacklistDictionary.Values.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Check if a Criterion should continue testing an IP Address.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <returns><c>true</c> if the Criterion should continue, otherwise <c>false</c>.</returns>
|
||||
public bool ShouldContinue(IPAddress address) =>
|
||||
!_whiteListBag.Contains(address) || !_blacklistDictionary.ContainsKey(address);
|
||||
|
||||
/// <summary>
|
||||
/// Purges this instance.
|
||||
/// </summary>
|
||||
public void Purge()
|
||||
{
|
||||
PurgeBlackList();
|
||||
|
||||
foreach (var criterion in _criterions)
|
||||
{
|
||||
criterion.PurgeData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the client.
|
||||
/// </summary>
|
||||
/// <param name="clientAddress">The client address.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task CheckClient(IPAddress clientAddress)
|
||||
{
|
||||
if (_whiteListBag.Contains(clientAddress))
|
||||
return;
|
||||
|
||||
foreach (var criterion in _criterions)
|
||||
{
|
||||
var result = await criterion.ValidateIPAddress(clientAddress).ConfigureAwait(false);
|
||||
|
||||
if (!result) continue;
|
||||
|
||||
TryBanIP(clientAddress, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_blacklistDictionary.ContainsKey(clientAddress))
|
||||
throw HttpException.Forbidden();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal async Task AddToWhitelistAsync(IEnumerable<string>? whitelist)
|
||||
{
|
||||
if (whitelist?.Any() != true)
|
||||
return;
|
||||
|
||||
foreach (var whiteAddress in whitelist)
|
||||
{
|
||||
var parsedAddresses = await IPParser.ParseAsync(whiteAddress).ConfigureAwait(false);
|
||||
foreach (var address in parsedAddresses.Where(x => !_whiteListBag.Contains(x)))
|
||||
{
|
||||
_whiteListBag.Add(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Lock() => LockConfiguration();
|
||||
|
||||
internal bool TryRemoveBlackList(IPAddress address)
|
||||
{
|
||||
foreach (var criterion in _criterions)
|
||||
{
|
||||
criterion.ClearIPAddress(address);
|
||||
}
|
||||
|
||||
return _blacklistDictionary.TryRemove(address, out _);
|
||||
}
|
||||
|
||||
internal void RegisterCriterion(IIPBanningCriterion criterion)
|
||||
{
|
||||
EnsureConfigurationNotLocked();
|
||||
_criterions.Add(criterion);
|
||||
}
|
||||
|
||||
internal bool TryBanIP(IPAddress address, bool isExplicit, DateTime? banUntil = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_blacklistDictionary.AddOrUpdate(address,
|
||||
k =>
|
||||
new BanInfo
|
||||
{
|
||||
IPAddress = k,
|
||||
ExpiresAt = banUntil?.Ticks ?? DateTime.Now.AddMinutes(_banTime).Ticks,
|
||||
IsExplicit = isExplicit,
|
||||
},
|
||||
(k, v) =>
|
||||
new BanInfo
|
||||
{
|
||||
IPAddress = k,
|
||||
ExpiresAt = banUntil?.Ticks ?? DateTime.Now.AddMinutes(_banTime).Ticks,
|
||||
IsExplicit = isExplicit,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_blacklistDictionary.Clear();
|
||||
|
||||
_criterions.ForEach(x => x.Dispose());
|
||||
_criterions.Clear();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void PurgeBlackList()
|
||||
{
|
||||
foreach (var k in _blacklistDictionary.Keys)
|
||||
{
|
||||
if (_blacklistDictionary.TryGetValue(k, out var info) &&
|
||||
DateTime.Now.Ticks > info.ExpiresAt)
|
||||
_blacklistDictionary.TryRemove(k, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Vendor/EmbedIO-3.5.2/Security/IPBanningModule.cs
vendored
Normal file
182
Vendor/EmbedIO-3.5.2/Security/IPBanningModule.cs
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EmbedIO.Security.Internal;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A module to ban clients by IP address, based on TCP requests-per-second or RegEx matches on log messages.
|
||||
/// </summary>
|
||||
/// <seealso cref="WebModuleBase" />
|
||||
public class IPBanningModule : WebModuleBase, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The default ban minutes.
|
||||
/// </summary>
|
||||
public const int DefaultBanMinutes = 30;
|
||||
|
||||
private const string NoConfigurationFound = "No configuration was found for the base route provided.";
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IPBanningModule" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="whitelist">A collection of valid IPs that never will be banned.</param>
|
||||
/// <param name="banMinutes">Minutes that an IP will remain banned.</param>
|
||||
public IPBanningModule(string baseRoute = "/",
|
||||
IEnumerable<string>? whitelist = null,
|
||||
int banMinutes = DefaultBanMinutes)
|
||||
: base(baseRoute)
|
||||
{
|
||||
Configuration = IPBanningExecutor.RetrieveInstance(baseRoute, banMinutes);
|
||||
|
||||
AddToWhitelist(whitelist);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="IPBanningModule"/> class.
|
||||
/// </summary>
|
||||
~IPBanningModule()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsFinalHandler => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client address.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The client address.
|
||||
/// </value>
|
||||
public IPAddress? ClientAddress { get; private set; }
|
||||
|
||||
internal IPBanningConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers the criterion.
|
||||
/// </summary>
|
||||
/// <param name="criterion">The criterion.</param>
|
||||
public void RegisterCriterion(IIPBanningCriterion criterion) =>
|
||||
Configuration.RegisterCriterion(criterion);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of current banned IPs.
|
||||
/// </summary>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="BanInfo" /> in the blacklist.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">baseRoute</exception>
|
||||
public static IEnumerable<BanInfo> GetBannedIPs(string baseRoute = "/") =>
|
||||
IPBanningExecutor.TryGetInstance(baseRoute, out var instance)
|
||||
? instance.BlackList
|
||||
: throw new ArgumentException(NoConfigurationFound, nameof(baseRoute));
|
||||
|
||||
/// <summary>
|
||||
/// Tries to ban an IP explicitly.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address to ban.</param>
|
||||
/// <param name="banMinutes">Minutes that the IP will remain banned.</param>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="isExplicit"><c>true</c> if the IP was explicitly banned.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP was added to the blacklist; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TryBanIP(IPAddress address, int banMinutes, string baseRoute = "/", bool isExplicit = true) =>
|
||||
TryBanIP(address, DateTime.Now.AddMinutes(banMinutes), baseRoute, isExplicit);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to ban an IP explicitly.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address to ban.</param>
|
||||
/// <param name="banDuration">A <see cref="TimeSpan" /> specifying the duration that the IP will remain banned.</param>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="isExplicit"><c>true</c> if the IP was explicitly banned.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP was added to the blacklist; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TryBanIP(IPAddress address, TimeSpan banDuration, string baseRoute = "/", bool isExplicit = true) =>
|
||||
TryBanIP(address, DateTime.Now.Add(banDuration), baseRoute, isExplicit);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to ban an IP explicitly.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address to ban.</param>
|
||||
/// <param name="banUntil">A <see cref="DateTime" /> specifying the expiration time of the ban.</param>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <param name="isExplicit"><c>true</c> if the IP was explicitly banned.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP was added to the blacklist; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">baseRoute</exception>
|
||||
public static bool TryBanIP(IPAddress address, DateTime banUntil, string baseRoute = "/", bool isExplicit = true)
|
||||
{
|
||||
if (!IPBanningExecutor.TryGetInstance(baseRoute, out var instance))
|
||||
throw new ArgumentException(NoConfigurationFound, nameof(baseRoute));
|
||||
|
||||
return instance.TryBanIP(address, isExplicit, banUntil);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to unban an IP explicitly.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address.</param>
|
||||
/// <param name="baseRoute">The base route.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the IP was removed from the blacklist; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">baseRoute</exception>
|
||||
public static bool TryUnbanIP(IPAddress address, string baseRoute = "/") =>
|
||||
IPBanningExecutor.TryGetInstance(baseRoute, out var instance)
|
||||
? instance.TryRemoveBlackList(address)
|
||||
: throw new ArgumentException(NoConfigurationFound, nameof(baseRoute));
|
||||
|
||||
internal void AddToWhitelist(IEnumerable<string>? whitelist) =>
|
||||
Configuration.AddToWhitelistAsync(whitelist).GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(CancellationToken cancellationToken)
|
||||
{
|
||||
Configuration.Lock();
|
||||
|
||||
base.OnStart(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task OnRequestAsync(IHttpContext context)
|
||||
{
|
||||
ClientAddress = context.Request.RemoteEndPoint.Address;
|
||||
return Configuration.CheckClient(ClientAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
IPBanningExecutor.TryRemoveInstance(BaseRoute);
|
||||
Configuration.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Vendor/EmbedIO-3.5.2/Security/IPBanningModuleExtensions.cs
vendored
Normal file
74
Vendor/EmbedIO-3.5.2/Security/IPBanningModuleExtensions.cs
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IPBanningModule"/> and derived classes.
|
||||
/// </summary>
|
||||
public static class IPBanningModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a collection of valid IPs that never will be banned.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">A collection of valid IPs that never will be banned.</param>
|
||||
/// <returns>
|
||||
/// <paramref name="this"/> with its whitelist configured.
|
||||
/// </returns>
|
||||
public static TModule WithWhitelist<TModule>(this TModule @this, params string[] value)
|
||||
where TModule : IPBanningModule
|
||||
{
|
||||
@this.AddToWhitelist(value);
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a collection of Regex to match the log messages against as a criterion for banning IP addresses.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="value">A collection of regex to match log messages against.</param>
|
||||
/// <returns>
|
||||
/// <paramref name="this"/> with a fail regex criterion configured.
|
||||
/// </returns>
|
||||
public static TModule WithRegexRules<TModule>(this TModule @this, params string[] value)
|
||||
where TModule : IPBanningModule =>
|
||||
WithRegexRules(@this, IPBanningRegexCriterion.DefaultMaxMatchCount, IPBanningRegexCriterion.DefaultSecondsMatchingPeriod, value);
|
||||
|
||||
/// <summary>
|
||||
/// Add a collection of Regex to match the log messages against as a criterion for banning IP addresses.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="maxMatchCount">The maximum match count.</param>
|
||||
/// <param name="secondsMatchingPeriod">The seconds matching period.</param>
|
||||
/// <param name="value">A collection of regex to match log messages against.</param>
|
||||
/// <returns>
|
||||
/// <paramref name="this" /> with a fail regex criterion configured.
|
||||
/// </returns>
|
||||
public static TModule WithRegexRules<TModule>(this TModule @this,
|
||||
int maxMatchCount,
|
||||
int secondsMatchingPeriod,
|
||||
params string[] value)
|
||||
where TModule : IPBanningModule
|
||||
{
|
||||
@this.RegisterCriterion(new IPBanningRegexCriterion(@this, value, maxMatchCount, secondsMatchingPeriod));
|
||||
return @this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a maximum amount of requests per second as a criterion for banning IP addresses.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModule">The type of the module.</typeparam>
|
||||
/// <param name="this">The module on which this method is called.</param>
|
||||
/// <param name="maxRequests">The maximum requests per second.</param>
|
||||
/// <returns>
|
||||
/// <paramref name="this"/> with a maximum requests per second configured.
|
||||
/// </returns>
|
||||
public static TModule WithMaxRequestsPerSecond<TModule>(this TModule @this, int maxRequests = IPBanningRequestsCriterion.DefaultMaxRequestsPerSecond)
|
||||
where TModule : IPBanningModule
|
||||
{
|
||||
@this.RegisterCriterion(new IPBanningRequestsCriterion(maxRequests));
|
||||
return @this;
|
||||
}
|
||||
}
|
||||
}
|
||||
197
Vendor/EmbedIO-3.5.2/Security/IPBanningRegexCriterion.cs
vendored
Normal file
197
Vendor/EmbedIO-3.5.2/Security/IPBanningRegexCriterion.cs
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
using Swan.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a log message regex matching criterion for <see cref="IPBanningModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IIPBanningCriterion" />
|
||||
public class IPBanningRegexCriterion : IIPBanningCriterion
|
||||
{
|
||||
/// <summary>
|
||||
/// The default matching period.
|
||||
/// </summary>
|
||||
public const int DefaultSecondsMatchingPeriod = 60;
|
||||
|
||||
/// <summary>
|
||||
/// The default maximum match count per period.
|
||||
/// </summary>
|
||||
public const int DefaultMaxMatchCount = 10;
|
||||
|
||||
private readonly ConcurrentDictionary<IPAddress, ConcurrentBag<long>> _failRegexMatches = new ConcurrentDictionary<IPAddress, ConcurrentBag<long>>();
|
||||
private readonly ConcurrentDictionary<string, Regex> _failRegex = new ConcurrentDictionary<string, Regex>();
|
||||
private readonly IPBanningModule _parent;
|
||||
private readonly int _secondsMatchingPeriod;
|
||||
private readonly int _maxMatchCount;
|
||||
private readonly ILogger? _innerLogger;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IPBanningRegexCriterion"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="rules">The rules.</param>
|
||||
/// <param name="maxMatchCount">The maximum match count.</param>
|
||||
/// <param name="secondsMatchingPeriod">The seconds matching period.</param>
|
||||
public IPBanningRegexCriterion(IPBanningModule parent, IEnumerable<string> rules, int maxMatchCount = DefaultMaxMatchCount, int secondsMatchingPeriod = DefaultSecondsMatchingPeriod)
|
||||
{
|
||||
_secondsMatchingPeriod = secondsMatchingPeriod;
|
||||
_maxMatchCount = maxMatchCount;
|
||||
_parent = parent;
|
||||
|
||||
AddRules(rules);
|
||||
|
||||
if (_failRegex.Any())
|
||||
_innerLogger = new InnerRegexCriterionLogger(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="IPBanningRegexCriterion"/> class.
|
||||
/// </summary>
|
||||
~IPBanningRegexCriterion()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> ValidateIPAddress(IPAddress address)
|
||||
{
|
||||
var minTime = DateTime.Now.AddSeconds(-1 * _secondsMatchingPeriod).Ticks;
|
||||
var shouldBan = _failRegexMatches.TryGetValue(address, out var attempts) &&
|
||||
attempts.Count(x => x >= minTime) >= _maxMatchCount;
|
||||
|
||||
return Task.FromResult(shouldBan);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearIPAddress(IPAddress address) =>
|
||||
_failRegexMatches.TryRemove(address, out _);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PurgeData()
|
||||
{
|
||||
var minTime = DateTime.Now.AddSeconds(-1 * _secondsMatchingPeriod).Ticks;
|
||||
|
||||
foreach (var k in _failRegexMatches.Keys)
|
||||
{
|
||||
if (!_failRegexMatches.TryGetValue(k, out var failRegexMatches)) continue;
|
||||
|
||||
var recentMatches = new ConcurrentBag<long>(failRegexMatches.Where(x => x >= minTime));
|
||||
if (!recentMatches.Any())
|
||||
_failRegexMatches.TryRemove(k, out _);
|
||||
else
|
||||
_failRegexMatches.AddOrUpdate(k, recentMatches, (x, y) => recentMatches);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_failRegexMatches.Clear();
|
||||
_failRegex.Clear();
|
||||
if (_innerLogger != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.UnregisterLogger(_innerLogger);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
_innerLogger.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void MatchIP(IPAddress address, string message)
|
||||
{
|
||||
if (!_parent.Configuration.ShouldContinue(address))
|
||||
return;
|
||||
|
||||
foreach (var regex in _failRegex.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!regex.IsMatch(message)) continue;
|
||||
|
||||
_failRegexMatches.GetOrAdd(address, new ConcurrentBag<long>()).Add(DateTime.Now.Ticks);
|
||||
break;
|
||||
}
|
||||
catch (RegexMatchTimeoutException ex)
|
||||
{
|
||||
$"Timeout trying to match '{ex.Input}' with pattern '{ex.Pattern}'.".Error(nameof(InnerRegexCriterionLogger));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddRules(IEnumerable<string> patterns)
|
||||
{
|
||||
foreach (var pattern in patterns)
|
||||
AddRule(pattern);
|
||||
}
|
||||
|
||||
private void AddRule(string pattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
_failRegex.TryAdd(pattern, new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(500)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(IPBanningModule), $"Invalid regex - '{pattern}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InnerRegexCriterionLogger : ILogger
|
||||
{
|
||||
private readonly IPBanningRegexCriterion _parent;
|
||||
|
||||
public InnerRegexCriterionLogger(IPBanningRegexCriterion parent)
|
||||
{
|
||||
_parent = parent;
|
||||
Logger.RegisterLogger(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public LogLevel LogLevel => LogLevel.Trace;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// DO nothing
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Log(LogMessageReceivedEventArgs logEvent)
|
||||
{
|
||||
var clientAddress = _parent._parent.ClientAddress;
|
||||
|
||||
if (clientAddress == null || string.IsNullOrWhiteSpace(logEvent.Message))
|
||||
return;
|
||||
|
||||
_parent.MatchIP(clientAddress, logEvent.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Vendor/EmbedIO-3.5.2/Security/IPBanningRequestsCriterion.cs
vendored
Normal file
93
Vendor/EmbedIO-3.5.2/Security/IPBanningRequestsCriterion.cs
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EmbedIO.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a maximun requests per second criterion for <see cref="IPBanningModule"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IIPBanningCriterion" />
|
||||
public class IPBanningRequestsCriterion : IIPBanningCriterion
|
||||
{
|
||||
/// <summary>
|
||||
/// The default maximum request per second.
|
||||
/// </summary>
|
||||
public const int DefaultMaxRequestsPerSecond = 50;
|
||||
|
||||
private static readonly ConcurrentDictionary<IPAddress, ConcurrentBag<long>> Requests = new ConcurrentDictionary<IPAddress, ConcurrentBag<long>>();
|
||||
|
||||
private readonly int _maxRequestsPerSecond;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal IPBanningRequestsCriterion(int maxRequestsPerSecond)
|
||||
{
|
||||
_maxRequestsPerSecond = maxRequestsPerSecond;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="IPBanningRequestsCriterion"/> class.
|
||||
/// </summary>
|
||||
~IPBanningRequestsCriterion()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> ValidateIPAddress(IPAddress address)
|
||||
{
|
||||
Requests.GetOrAdd(address, new ConcurrentBag<long>()).Add(DateTime.Now.Ticks);
|
||||
|
||||
var lastSecond = DateTime.Now.AddSeconds(-1).Ticks;
|
||||
var lastMinute = DateTime.Now.AddMinutes(-1).Ticks;
|
||||
|
||||
var shouldBan = Requests.TryGetValue(address, out var attempts) &&
|
||||
(attempts.Count(x => x >= lastSecond) >= _maxRequestsPerSecond ||
|
||||
(attempts.Count(x => x >= lastMinute) / 60) >= _maxRequestsPerSecond);
|
||||
|
||||
return Task.FromResult(shouldBan);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearIPAddress(IPAddress address) =>
|
||||
Requests.TryRemove(address, out _);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PurgeData()
|
||||
{
|
||||
var minTime = DateTime.Now.AddMinutes(-1).Ticks;
|
||||
|
||||
foreach (var k in Requests.Keys)
|
||||
{
|
||||
if (!Requests.TryGetValue(k, out var requests)) continue;
|
||||
|
||||
var recentRequests = new ConcurrentBag<long>(requests.Where(x => x >= minTime));
|
||||
if (!recentRequests.Any())
|
||||
Requests.TryRemove(k, out _);
|
||||
else
|
||||
Requests.AddOrUpdate(k, recentRequests, (x, y) => recentRequests);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
Requests.Clear();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Vendor/EmbedIO-3.5.2/Security/Internal/IPBanningExecutor.cs
vendored
Normal file
31
Vendor/EmbedIO-3.5.2/Security/Internal/IPBanningExecutor.cs
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
using Swan.Threading;
|
||||
|
||||
namespace EmbedIO.Security.Internal
|
||||
{
|
||||
internal static class IPBanningExecutor
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, IPBanningConfiguration> Configurations = new ConcurrentDictionary<string, IPBanningConfiguration>();
|
||||
|
||||
private static readonly PeriodicTask Purger = new PeriodicTask(TimeSpan.FromMinutes(1), ct => {
|
||||
foreach (var conf in Configurations.Keys)
|
||||
{
|
||||
if (Configurations.TryGetValue(conf, out var instance))
|
||||
instance.Purge();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
public static IPBanningConfiguration RetrieveInstance(string baseRoute, int banMinutes) =>
|
||||
Configurations.GetOrAdd(baseRoute, x => new IPBanningConfiguration(banMinutes));
|
||||
|
||||
public static bool TryGetInstance(string baseRoute, out IPBanningConfiguration configuration) =>
|
||||
Configurations.TryGetValue(baseRoute, out configuration);
|
||||
|
||||
public static bool TryRemoveInstance(string baseRoute) =>
|
||||
Configurations.TryRemove(baseRoute, out _);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user