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