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
{
///
/// Represents a configuration object for .
///
///
public class IPBanningConfiguration : ConfiguredObject, IDisposable
{
private readonly List _criterions = new List();
private readonly ConcurrentDictionary _blacklistDictionary = new ConcurrentDictionary();
private readonly ConcurrentBag _whiteListBag = new ConcurrentBag();
private readonly int _banTime;
private bool _disposed;
internal IPBanningConfiguration(int banTime)
{
_banTime = banTime;
}
///
/// Finalizes an instance of the class.
///
~IPBanningConfiguration()
{
Dispose(false);
}
///
/// Gets the black list.
///
///
/// The black list.
///
public List BlackList => _blacklistDictionary.Values.ToList();
///
/// Check if a Criterion should continue testing an IP Address.
///
/// The address.
/// true if the Criterion should continue, otherwise false.
public bool ShouldContinue(IPAddress address) =>
!_whiteListBag.Contains(address) || !_blacklistDictionary.ContainsKey(address);
///
/// Purges this instance.
///
public void Purge()
{
PurgeBlackList();
foreach (var criterion in _criterions)
{
criterion.PurgeData();
}
}
///
/// Checks the client.
///
/// The client address.
/// A representing the asynchronous operation.
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();
}
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
internal async Task AddToWhitelistAsync(IEnumerable? 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;
}
}
///
/// 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)
{
_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 _);
}
}
}
}