diff --git a/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs index e4aba0cd..b2c78ebd 100644 --- a/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs +++ b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs @@ -11,6 +11,7 @@ using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using SharedLibraryCore.QueryHelper; +using SharedLibraryCore.Services; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.Meta @@ -19,13 +20,14 @@ namespace IW4MAdmin.Application.Meta /// implementation of IResourceQueryHelper /// used to pull in penalties applied to a given client id /// - public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper + public class + ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper { private readonly ILogger _logger; private readonly IDatabaseContextFactory _contextFactory; private readonly ApplicationConfiguration _appConfig; - public ReceivedPenaltyResourceQueryHelper(ILogger logger, + public ReceivedPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig) { _contextFactory = contextFactory; @@ -33,28 +35,36 @@ namespace IW4MAdmin.Application.Meta _appConfig = appConfig; } - public async Task> QueryResource(ClientPaginationRequest query) + public async Task> QueryResource( + ClientPaginationRequest query) { var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); await using var ctx = _contextFactory.CreateContext(enableTracking: false); var linkId = await ctx.Clients.AsNoTracking() - .Where(_client => _client.ClientId == query.ClientId) - .Select(_client => new {_client.AliasLinkId, _client.CurrentAliasId }) - .FirstOrDefaultAsync(); + .Where(_client => _client.ClientId == query.ClientId) + .Select(_client => new { _client.AliasLinkId, _client.CurrentAliasId }) + .FirstOrDefaultAsync(); var iqPenalties = ctx.Penalties.AsNoTracking() .Where(_penalty => _penalty.OffenderId == query.ClientId || linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId); IQueryable iqIpLinkedPenalties = null; - + IQueryable identifierPenalties = null; + if (!_appConfig.EnableImplicitAccountLinking) { var usedIps = await ctx.Aliases.AsNoTracking() - .Where(alias => (alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) && alias.IPAddress != null) + .Where(alias => + (alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) && + alias.IPAddress != null) .Select(alias => alias.IPAddress).ToListAsync(); + identifierPenalties = ctx.PenaltyIdentifiers.AsNoTracking().Where(identifier => + identifier.IPv4Address != null && usedIps.Contains(identifier.IPv4Address)) + .Select(id => id.Penalty); + var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress)) .Select(alias => alias.LinkId) .ToListAsync(); @@ -65,13 +75,18 @@ namespace IW4MAdmin.Application.Meta } var iqAllPenalties = iqPenalties; - + if (iqIpLinkedPenalties != null) { iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties); } - var penalties = await iqAllPenalties + if (identifierPenalties != null) + { + iqAllPenalties = iqPenalties.Union(identifierPenalties); + } + + var penalties = await iqAllPenalties .Where(_penalty => _penalty.When < query.Before) .OrderByDescending(_penalty => _penalty.When) .Take(query.Count) @@ -97,7 +112,7 @@ namespace IW4MAdmin.Application.Meta { // todo: maybe actually count RetrievedResultCount = penalties.Count, - Results = penalties + Results = penalties.Distinct() }; } } diff --git a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs index c397b172..83f3074b 100644 --- a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs +++ b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs @@ -153,6 +153,7 @@ namespace SharedLibraryCore.Configuration public bool EnablePrivilegedUserPrivacy { get; set; } [ConfigurationIgnore] public bool EnableImplicitAccountLinking { get; set; } = false; + [ConfigurationIgnore] public TimeSpan RecentAliasIpLinkTimeLimit { get; set; } = TimeSpan.FromDays(7); [ConfigurationIgnore] public TimeSpan MaxClientHistoryTime { get; set; } = TimeSpan.FromHours(12); @@ -230,4 +231,4 @@ namespace SharedLibraryCore.Configuration return "ApplicationConfiguration"; } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index 7baad66b..4a4c8ad7 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -693,13 +693,14 @@ namespace SharedLibraryCore.Database.Models if (Level != Permission.Banned) { Utilities.DefaultLogger.LogInformation( - "Client {client} has a ban penalty, but they're using a new GUID, we we're updating their level and kicking them", + "Client {Client} has a ban penalty, but they're using a new GUID, we we're updating their level and kicking them", ToString()); + await SetLevel(Permission.Banned, autoKickClient).WaitAsync(Utilities.DefaultCommandTimeout, CurrentServer.Manager.CancellationToken); } - Utilities.DefaultLogger.LogInformation("Kicking {client} because they are banned", ToString()); + Utilities.DefaultLogger.LogInformation("Kicking {Client} because they are banned", ToString()); Kick(loc["WEBFRONT_PENALTY_LIST_BANNED_REASON"], autoKickClient, banPenalty); return false; } @@ -732,6 +733,34 @@ namespace SharedLibraryCore.Database.Models ToString()); Unflag(Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTOFLAG_UNFLAG"], autoKickClient); } + + if (Level != Permission.Banned) + { + return true; + } + + // we want to see if they've recently used a banned IP + var recentIPPenalties= await CurrentServer.Manager.GetPenaltyService().ActivePenaltiesByRecentIdentifiers(AliasLinkId); + + var recentBanPenalty = + recentIPPenalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban); + + if (recentBanPenalty is null || !IPAddress.HasValue) + { + Utilities.DefaultLogger.LogInformation( + "Setting {Client} level to user because they are banned but no direct penalties or recent penalty identifiers exist for them", + ToString()); + await SetLevel(Permission.User, autoKickClient).WaitAsync(Utilities.DefaultCommandTimeout, + CurrentServer.Manager.CancellationToken); + return true; + } + + Utilities.DefaultLogger.LogInformation("Updating penalty for {Client} because they recently used a banned IP", this); + await CurrentServer.Manager.GetPenaltyService() + .CreatePenaltyIdentifier(recentBanPenalty.PenaltyId, NetworkId, IPAddress.Value); + + Utilities.DefaultLogger.LogInformation("Kicking {Client} because they are banned", ToString()); + Kick(loc["WEBFRONT_PENALTY_LIST_BANNED_REASON"], autoKickClient, recentBanPenalty); } return true; diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 5d74fc45..5ba26666 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -59,6 +59,20 @@ namespace SharedLibraryCore.Services return newEntity; } + public async Task CreatePenaltyIdentifier(int penaltyId, long networkId, int ipv4Address) + { + await using var context = _contextFactory.CreateContext(); + var penaltyIdentifiers = new EFPenaltyIdentifier + { + PenaltyId = penaltyId, + NetworkId = networkId, + IPv4Address = ipv4Address + }; + + context.PenaltyIdentifiers.Add(penaltyIdentifiers); + await context.SaveChangesAsync(); + } + public Task Delete(EFPenalty entity) { throw new NotImplementedException(); @@ -172,12 +186,34 @@ namespace SharedLibraryCore.Services public async Task> GetActivePenaltiesByIdentifier(int? ip, long networkId) { await using var context = _contextFactory.CreateContext(false); + var activePenaltiesIds = context.PenaltyIdentifiers.Where(identifier => identifier.IPv4Address != null && identifier.IPv4Address == ip || identifier.NetworkId == networkId) .Where(FilterById); return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } + public async Task> ActivePenaltiesByRecentIdentifiers(int linkId) + { + await using var context = _contextFactory.CreateContext(false); + + var recentlyUsedIps = await context.Aliases.Where(alias => alias.LinkId == linkId) + .Where(alias => alias.IPAddress != null) + .Where(alias => alias.DateAdded >= DateTime.UtcNow - _appConfig.RecentAliasIpLinkTimeLimit) + .Select(alias => alias.IPAddress).ToListAsync(); + + if (!recentlyUsedIps.Any()) + { + return new List(); + } + + var activePenaltiesIds = context.PenaltyIdentifiers + .Where(identifier => recentlyUsedIps.Contains(identifier.IPv4Address)) + .Where(FilterById); + + return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); + } + public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, int? ipAddress = null) { await using var context = _contextFactory.CreateContext();