using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using EmbedIO.Security.Internal;
namespace EmbedIO.Security
{
///
/// A module to ban clients by IP address, based on TCP requests-per-second or RegEx matches on log messages.
///
///
public class IPBanningModule : WebModuleBase, IDisposable
{
///
/// The default ban minutes.
///
public const int DefaultBanMinutes = 30;
private const string NoConfigurationFound = "No configuration was found for the base route provided.";
private bool _disposed;
///
/// Initializes a new instance of the class.
///
/// The base route.
/// A collection of valid IPs that never will be banned.
/// Minutes that an IP will remain banned.
public IPBanningModule(string baseRoute = "/",
IEnumerable? whitelist = null,
int banMinutes = DefaultBanMinutes)
: base(baseRoute)
{
Configuration = IPBanningExecutor.RetrieveInstance(baseRoute, banMinutes);
AddToWhitelist(whitelist);
}
///
/// Finalizes an instance of the class.
///
~IPBanningModule()
{
Dispose(false);
}
///
public override bool IsFinalHandler => false;
///
/// Gets the client address.
///
///
/// The client address.
///
public IPAddress? ClientAddress { get; private set; }
internal IPBanningConfiguration Configuration { get; }
///
/// Registers the criterion.
///
/// The criterion.
public void RegisterCriterion(IIPBanningCriterion criterion) =>
Configuration.RegisterCriterion(criterion);
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Gets the list of current banned IPs.
///
/// The base route.
///
/// A collection of in the blacklist.
///
/// baseRoute
public static IEnumerable GetBannedIPs(string baseRoute = "/") =>
IPBanningExecutor.TryGetInstance(baseRoute, out var instance)
? instance.BlackList
: throw new ArgumentException(NoConfigurationFound, nameof(baseRoute));
///
/// Tries to ban an IP explicitly.
///
/// The IP address to ban.
/// Minutes that the IP will remain banned.
/// The base route.
/// true if the IP was explicitly banned.
///
/// true if the IP was added to the blacklist; otherwise, false.
///
public static bool TryBanIP(IPAddress address, int banMinutes, string baseRoute = "/", bool isExplicit = true) =>
TryBanIP(address, DateTime.Now.AddMinutes(banMinutes), baseRoute, isExplicit);
///
/// Tries to ban an IP explicitly.
///
/// The IP address to ban.
/// A specifying the duration that the IP will remain banned.
/// The base route.
/// true if the IP was explicitly banned.
///
/// true if the IP was added to the blacklist; otherwise, false.
///
public static bool TryBanIP(IPAddress address, TimeSpan banDuration, string baseRoute = "/", bool isExplicit = true) =>
TryBanIP(address, DateTime.Now.Add(banDuration), baseRoute, isExplicit);
///
/// Tries to ban an IP explicitly.
///
/// The IP address to ban.
/// A specifying the expiration time of the ban.
/// The base route.
/// true if the IP was explicitly banned.
///
/// true if the IP was added to the blacklist; otherwise, false.
///
/// baseRoute
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);
}
///
/// Tries to unban an IP explicitly.
///
/// The IP address.
/// The base route.
///
/// true if the IP was removed from the blacklist; otherwise, false.
///
/// baseRoute
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? whitelist) =>
Configuration.AddToWhitelistAsync(whitelist).GetAwaiter().GetResult();
///
protected override void OnStart(CancellationToken cancellationToken)
{
Configuration.Lock();
base.OnStart(cancellationToken);
}
///
protected override Task OnRequestAsync(IHttpContext context)
{
ClientAddress = context.Request.RemoteEndPoint.Address;
return Configuration.CheckClient(ClientAddress);
}
///
/// Releases unmanaged and - optionally - managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
IPBanningExecutor.TryRemoveInstance(BaseRoute);
Configuration.Dispose();
}
_disposed = true;
}
}
}