From 5f4171ccf4480ab3248b7ca6ed63eb5fe08d97f9 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Dec 2018 12:43:40 -0600 Subject: [PATCH] hopefulyl fix aliasing issue bans are applied to an account if the accounts are linked but penallty on a different accounts --- Application/IW4MServer.cs | 35 +--- SharedLibraryCore/Objects/EFClient.cs | 79 ++++++++-- SharedLibraryCore/Services/ClientService.cs | 158 +++++++++---------- SharedLibraryCore/Services/PenaltyService.cs | 98 +++++++++--- WebfrontCore/Controllers/ClientController.cs | 4 +- 5 files changed, 226 insertions(+), 148 deletions(-) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index cd10be1a..59cccb76 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -64,23 +64,12 @@ namespace IW4MAdmin client.CurrentServer = this; Clients[client.ClientNumber] = client; - - // this only happens if the preconnect event occurred from RCon polling - if (clientFromLog.IPAddress.HasValue) - { - // they're banned so we don't want to add them as connected - if (!await client.OnJoin(clientFromLog.IPAddress)) - { - return; - } - } - - client.OnConnect(); + await client.OnJoin(clientFromLog.IPAddress); client.State = EFClient.ClientState.Connected; #if DEBUG == true Logger.WriteDebug($"End PreConnect for {client}"); -#endif +#endif var e = new GameEvent() { Origin = client, @@ -231,14 +220,11 @@ namespace IW4MAdmin Offender = E.Target, Offense = E.Data, Punisher = E.Origin, - Active = true, When = DateTime.UtcNow, Link = E.Target.AliasLink }; var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty); - E.Target.ReceivedPenalties.Add(addedPenalty); - await Manager.GetClientService().Update(E.Target); } @@ -264,8 +250,8 @@ namespace IW4MAdmin else if (E.Type == GameEvent.EventType.Ban) { - - await Ban(E.Data, E.Target, E.Origin, E.Extra as bool? ?? false); + bool isEvade = E.Extra != null ? (bool)E.Extra : false; + await Ban(E.Data, E.Target, E.Origin, isEvade); } else if (E.Type == GameEvent.EventType.Unban) @@ -307,7 +293,7 @@ namespace IW4MAdmin else if (E.Type == GameEvent.EventType.PreDisconnect) { - if ((DateTime.UtcNow - SessionStart).TotalSeconds < 5) + if ((DateTime.UtcNow - SessionStart).TotalSeconds < 10) { Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end"); E.FailReason = GameEvent.EventFailReason.Invalid; @@ -840,7 +826,7 @@ namespace IW4MAdmin return; } - String message = $"^1{loc["SERVER_WARNING"]} ^7[^3{Target.Warnings}^7]: ^3{Target.Name}^7, {Reason}"; + string message = $"^1{loc["SERVER_WARNING"]} ^7[^3{Target.Warnings}^7]: ^3{Target.Name}^7, {Reason}"; Target.CurrentServer.Broadcast(message); } @@ -849,10 +835,8 @@ namespace IW4MAdmin Type = Penalty.PenaltyType.Warning, Expires = DateTime.UtcNow, Offender = Target, + Punisher = Utilities.IW4MAdminClient(this), Offense = Reason, - Punisher = Origin, - Active = true, - When = DateTime.UtcNow, Link = Target.AliasLink }; @@ -892,7 +876,6 @@ namespace IW4MAdmin Offender = Target, Offense = Reason, Punisher = Origin, - When = DateTime.UtcNow, Link = Target.AliasLink }; @@ -930,8 +913,6 @@ namespace IW4MAdmin Offender = Target, Offense = Reason, Punisher = Origin, - Active = true, - When = DateTime.UtcNow, Link = Target.AliasLink }; @@ -977,8 +958,6 @@ namespace IW4MAdmin Offender = targetClient, Offense = reason, Punisher = originClient, - Active = true, - When = DateTime.UtcNow, Link = targetClient.AliasLink, AutomatedOffense = originClient.AdministeredPenalties?.FirstOrDefault()?.AutomatedOffense, IsEvadedOffense = isEvade diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index 8b5377cb..6469e56a 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -406,6 +406,9 @@ namespace SharedLibraryCore.Database.Models { var loc = Utilities.CurrentLocalization.LocalizationIndex; + LastConnection = DateTime.UtcNow; + Connections += 1; + if (Name.Length < 3) { CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is too short"); @@ -430,6 +433,7 @@ namespace SharedLibraryCore.Database.Models } // reserved slots stuff + // todo: is this broken on T6? if (CurrentServer.MaxClients - (CurrentServer.GetClientsAsList().Count(_client => !_client.IsPrivileged())) < CurrentServer.ServerConfig.ReservedSlotNumber && !this.IsPrivileged()) { @@ -437,9 +441,6 @@ namespace SharedLibraryCore.Database.Models Kick(loc["SERVER_KICK_SLOT_IS_RESERVED"], Utilities.IW4MAdminClient(CurrentServer)); return; } - - LastConnection = DateTime.UtcNow; - Connections += 1; } public async Task OnDisconnect() @@ -452,13 +453,65 @@ namespace SharedLibraryCore.Database.Models public async Task OnJoin(int? ipAddress) { + CurrentServer.Logger.WriteDebug($"Start join for {this}::{ipAddress}::{Level.ToString()}"); + IPAddress = ipAddress; + var loc = Utilities.CurrentLocalization.LocalizationIndex; + var autoKickClient = Utilities.IW4MAdminClient(CurrentServer); await CurrentServer.Manager.GetClientService().UpdateAlias(this); - var loc = Utilities.CurrentLocalization.LocalizationIndex; + OnConnect(); + + CurrentServer.Logger.WriteDebug($"OnConnect finished for {this}"); + + #region CLIENT_BAN + // kick them as their level is banned + if (Level == Permission.Banned) + { + CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned"); + var ban = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Expires == null && _penalty.Active); + + if (ban == null) + { + // this is from the old system before bans were applied to all accounts + ban = (await CurrentServer.Manager + .GetPenaltyService() + .GetActivePenaltiesAsync(AliasLinkId)) + .FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Ban); + + CurrentServer.Logger.WriteError($"Client {this} is banned, but no penalty exists for their ban"); + + // hack: re apply the automated offense to the reban + if (ban.AutomatedOffense != null) + { + autoKickClient.AdministeredPenalties?.Add(new EFPenalty() + { + AutomatedOffense = ban.AutomatedOffense + }); + } + // this is a reban of the new GUID and IP + Ban($"{ban.Offense}", autoKickClient, false); + return false; + } + + Kick($"{loc["SERVER_BAN_PREV"]} {ban?.Offense}", autoKickClient); + return false; + } + + var tempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan && _penalty.Expires > DateTime.UtcNow && _penalty.Active); + // they have an active tempban tied to their GUID + if (tempBan != null) + { + CurrentServer.Logger.WriteDebug($"Kicking {this} because they are temporarily banned"); + Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); + return false; + } + #endregion + + // we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID) var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress); - var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan); + var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban); var currentAutoFlag = activePenalties.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1) .Where(p => p.Active) @@ -475,14 +528,13 @@ namespace SharedLibraryCore.Database.Models if (currentBan != null) { - CurrentServer.Logger.WriteInfo($"Banned client {this} trying to join..."); - var autoKickClient = Utilities.IW4MAdminClient(CurrentServer); + CurrentServer.Logger.WriteInfo($"Banned client {this} trying to evade..."); // reban the "evading" guid - if (Level != Permission.Banned && - currentBan.Type == Penalty.PenaltyType.Ban) + if (Level != Permission.Banned) { CurrentServer.Logger.WriteInfo($"Banned client {this} connected using a new GUID"); + // hack: re apply the automated offense to the reban if (currentBan.AutomatedOffense != null) { @@ -491,18 +543,13 @@ namespace SharedLibraryCore.Database.Models AutomatedOffense = currentBan.AutomatedOffense }); } + // this is a reban of the new GUID and IP Ban($"{currentBan.Offense}", autoKickClient, true); } - // the player is permanently banned - else if (currentBan.Type == Penalty.PenaltyType.Ban) - { - Kick($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense} ({loc["SERVER_BAN_APPEAL"]} {CurrentServer.Website})", autoKickClient); - } - else { - Kick($"{loc["SERVER_TB_REMAIN"]} ({(currentBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); + CurrentServer.Logger.WriteError($"Banned client {this} is banned but, no ban penalty was found (2)"); } return false; diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index e72bd65e..7cfcfc85 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -29,6 +29,7 @@ namespace SharedLibraryCore.Services { Active = false }, + ReceivedPenalties = new List() }; client.CurrentAlias = new Alias() @@ -63,14 +64,15 @@ namespace SharedLibraryCore.Services string name = entity.Name; int? ip = entity.IPAddress; + // indicates if someone appears to have played before bool hasExistingAlias = false; - // get all aliases by IP + // get all aliases by IP address and LinkId var iqAliases = context.Aliases .Include(a => a.Link) .Where(a => a.Link.Active) .Where(a => (a.IPAddress != null && a.IPAddress == ip) || - a.LinkId == entity.AliasLinkId); + a.LinkId == entity.AliasLinkId); #if DEBUG == true var aliasSql = iqAliases.ToSql(); @@ -78,102 +80,97 @@ namespace SharedLibraryCore.Services var aliases = await iqAliases.ToListAsync(); // see if they have a matching IP + Name but new NetworkId - var existingAlias = aliases.FirstOrDefault(a => a.Name == name); + var existingAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip); + bool exactAliasMatch = existingAlias != null; // if existing alias matches link them EFAliasLink aliasLink = existingAlias?.Link; // if no exact matches find the first IP that matches aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link; - // if no exact or IP matches, create new link + // if no matches are found, create new link aliasLink = aliasLink ?? new EFAliasLink(); - // this has to be set here because we can't evalute it properly later - hasExistingAlias = existingAlias != null; + hasExistingAlias = aliases.Count > 0; - if (hasExistingAlias && !entity.AliasLink.Active) + // the existing alias matches ip and name, so we can just ignore the temporary one + if (exactAliasMatch) { - entity.CurrentServer.Logger.WriteDebug($"Removing temporary alias for ${entity}"); - - // we want to delete the temporary alias - context.Entry(entity.CurrentAlias).State = EntityState.Deleted; - entity.CurrentAlias = null; - - // we want to delete the temporary alias link - context.Entry(entity.AliasLink).State = EntityState.Deleted; - entity.AliasLink = null; - - // they have an existing alias so assign it - entity.CurrentAlias = existingAlias; - entity.AliasLink = aliasLink; - - await context.SaveChangesAsync(); - - entity.AliasLinkId = aliasLink.AliasLinkId; - } - - // update the temporary alias to permanent one - else if (!entity.AliasLink.Active) - { - entity.CurrentServer.Logger.WriteDebug($"Linking permanent alias for ${entity}"); - - // we want to track the current alias and link - var alias = context.Update(entity.CurrentAlias).Entity; - var _aliasLink = context.Update(entity.AliasLink).Entity; - - alias.Active = true; - alias.IPAddress = ip; - alias.Name = name; - _aliasLink.Active = true; - - existingAlias = alias; - aliasLink = _aliasLink; - await context.SaveChangesAsync(); - - entity.AliasLinkId = aliasLink.AliasLinkId; - } - - // if no existing alias create new alias - existingAlias = existingAlias ?? new EFAlias() - { - DateAdded = DateTime.UtcNow, - IPAddress = ip, - Link = aliasLink, - Name = name, - }; - - if (!hasExistingAlias) - { - entity.CurrentServer.Logger.WriteDebug($"Connecting player does not have an existing alias {entity}"); - } - - else - { - var linkIds = aliases.Select(a => a.LinkId); - - if (linkIds.Count() > 0) + // they're using the same alias as before, so we need to make sure the current aliases is set to it + if (entity.CurrentAliasId != existingAlias.AliasId) { - var highestLevel = await context.Clients - .Where(c => linkIds.Contains(c.AliasLinkId)) - .MaxAsync(c => c.Level); + context.Update(entity); - if (entity.Level != highestLevel) - { - context.Update(entity); - entity.Level = highestLevel; - await context.SaveChangesAsync(); - } + entity.CurrentAlias = existingAlias; + entity.CurrentAliasId = existingAlias.AliasId; } } - if (entity.CurrentAlias != existingAlias || - entity.AliasLink != aliasLink) + // theres no exact match, but they've played before with the GUID or IP + else if (hasExistingAlias) { - entity.CurrentAlias = existingAlias; - entity.AliasLink = aliasLink; + // the current link is temporary so we need to update + if (!entity.AliasLink.Active) + { + // we want to delete the temporary alias link + context.Entry(entity.AliasLink).State = EntityState.Deleted; + context.Update(entity); + entity.AliasLink = aliasLink; + entity.AliasLinkId = aliasLink.AliasLinkId; + + await context.SaveChangesAsync(); + } + + // they have an existing link context.Update(entity); + entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}"); + entity.CurrentAlias = new EFAlias() + { + DateAdded = DateTime.UtcNow, + IPAddress = ip, + Link = aliasLink, + LinkId = aliasLink.AliasLinkId, + Name = name + }; + entity.AliasLink.Children.Add(entity.CurrentAlias); + await context.SaveChangesAsync(); + } + + // no record of them playing + else + { + context.Update(entity); + context.Update(entity.AliasLink); + entity.CurrentAlias = new EFAlias() + { + DateAdded = DateTime.UtcNow, + IPAddress = ip, + Link = aliasLink, + Name = name + }; + + entity.AliasLink.Active = true; + entity.AliasLink.Children.Add(entity.CurrentAlias); + + await context.SaveChangesAsync(); + } + + var linkIds = aliases.Select(a => a.LinkId); + + if (linkIds.Count() > 0) + { + var highestLevel = await context.Clients + .Where(c => linkIds.Contains(c.AliasLinkId)) + .MaxAsync(c => c.Level); + + if (entity.Level != highestLevel) + { + // todo: log level changes here + context.Update(entity); + entity.Level = highestLevel; + await context.SaveChangesAsync(); + } } - await context.SaveChangesAsync(); } } @@ -251,6 +248,7 @@ namespace SharedLibraryCore.Services context.Clients .Include(c => c.CurrentAlias) .Include(c => c.AliasLink.Children) + .Include(c => c.ReceivedPenalties) .FirstOrDefault(c => c.NetworkId == networkId) ); diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index b8434b71..b7339f5b 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -18,38 +18,86 @@ namespace SharedLibraryCore.Services { using (var context = new DatabaseContext()) { - // create the actual EFPenalty - EFPenalty addedEntity = new EFPenalty() - { - OffenderId = newEntity.Offender.ClientId, - PunisherId = newEntity.Punisher.ClientId, - LinkId = newEntity.Link.AliasLinkId, - Type = newEntity.Type, - Expires = newEntity.Expires, - Offense = newEntity.Offense, - When = newEntity.When, - AutomatedOffense = newEntity.AutomatedOffense - }; + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.ChangeTracker.LazyLoadingEnabled = true; + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; // make bans propogate to all aliases - if (addedEntity.Type == Objects.Penalty.PenaltyType.Ban) + if (newEntity.Type == Penalty.PenaltyType.Ban) { await context.Clients - .Where(c => c.AliasLinkId == addedEntity.LinkId) - .ForEachAsync(c => c.Level = Permission.Banned); + .Include(c => c.ReceivedPenalties) + .Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId) + .ForEachAsync(c => + { + if (c.Level != Permission.Banned) + { + c.Level = Permission.Banned; + c.ReceivedPenalties.Add(new EFPenalty() + { + Active = true, + OffenderId = c.ClientId, + PunisherId = newEntity.Punisher.ClientId, + LinkId = c.AliasLinkId, + Type = newEntity.Type, + Expires = newEntity.Expires, + Offense = newEntity.Offense, + When = DateTime.UtcNow, + AutomatedOffense = newEntity.AutomatedOffense, + IsEvadedOffense = newEntity.IsEvadedOffense + }); + } + }); } // make flags propogate to all aliases - else if (addedEntity.Type == Objects.Penalty.PenaltyType.Flag) + else if (newEntity.Type == Penalty.PenaltyType.Flag) { await context.Clients - .Where(c => c.AliasLinkId == addedEntity.LinkId) - .ForEachAsync(c => c.Level = Permission.Flagged); + .Include(c => c.ReceivedPenalties) + .Where(c => c.AliasLinkId == newEntity.LinkId) + .ForEachAsync(c => + { + if (c.Level != Permission.Flagged) + { + c.Level = Permission.Flagged; + c.ReceivedPenalties.Add(new EFPenalty() + { + Active = true, + OffenderId = c.ClientId, + PunisherId = newEntity.Punisher.ClientId, + LinkId = c.AliasLinkId, + Type = newEntity.Type, + Expires = newEntity.Expires, + Offense = newEntity.Offense, + When = DateTime.UtcNow, + AutomatedOffense = newEntity.AutomatedOffense, + IsEvadedOffense = newEntity.IsEvadedOffense + }); + } + }); + } + + // we just want to add it to the database + else + { + context.Penalties.Add(new EFPenalty() + { + Active = true, + OffenderId = newEntity.Offender.ClientId, + PunisherId = newEntity.Punisher.ClientId, + LinkId = newEntity.Link.AliasLinkId, + Type = newEntity.Type, + Expires = newEntity.Expires, + Offense = newEntity.Offense, + When = DateTime.UtcNow, + AutomatedOffense = newEntity.AutomatedOffense, + IsEvadedOffense = newEntity.IsEvadedOffense + }); } - context.Penalties.Add(addedEntity); await context.SaveChangesAsync(); - return addedEntity; + return newEntity; } } @@ -228,7 +276,8 @@ namespace SharedLibraryCore.Services Expression> filter = (p) => new Penalty.PenaltyType[] { Penalty.PenaltyType.TempBan, - Penalty.PenaltyType.Ban, Penalty.PenaltyType.Flag + Penalty.PenaltyType.Ban, + Penalty.PenaltyType.Flag }.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > now); @@ -240,7 +289,7 @@ namespace SharedLibraryCore.Services .Where(filter); var iqIPPenalties = context.Aliases - .Where(a => a.IPAddress == ip) + .Where(a => a.IPAddress != null & a.IPAddress == ip) .SelectMany(a => a.Link.ReceivedPenalties) .Where(filter); @@ -249,7 +298,10 @@ namespace SharedLibraryCore.Services var ipPenaltiesSql = iqIPPenalties.ToSql(); #endif - var activePenalties = (await iqLinkPenalties.ToListAsync()).Union(await iqIPPenalties.ToListAsync()); + var activePenalties = (await iqLinkPenalties.ToListAsync()) + .Union(await iqIPPenalties.ToListAsync()) + .Distinct(); + // this is a bit more performant in memory (ordering) return activePenalties.OrderByDescending(p => p.When).ToList(); } diff --git a/WebfrontCore/Controllers/ClientController.cs b/WebfrontCore/Controllers/ClientController.cs index ee78921f..4e9daa81 100644 --- a/WebfrontCore/Controllers/ClientController.cs +++ b/WebfrontCore/Controllers/ClientController.cs @@ -43,10 +43,12 @@ namespace WebfrontCore.Controllers .Where(a => a.Name != client.Name) .Select(a => a.Name) .Distinct() - .OrderBy(a => a) + .OrderBy(a => a) .ToList(), IPs = client.AliasLink.Children .Select(i => i.IPAddress.ConvertIPtoString()) + .Union(new List() { client.CurrentAlias.IPAddress.ConvertIPtoString() }) + .Where(i => !string.IsNullOrEmpty(i)) .Distinct() .OrderBy(i => i) .ToList(),