From 00634780d47dee472651c024b26e474c632a012c Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 2 Apr 2019 20:20:37 -0500 Subject: [PATCH] use "world client" when recieving fall damage/damage fix rare bug with GetClientByName refactor some alias/ef stuff. still more to do --- Application/ApplicationManager.cs | 5 + Application/EventParsers/BaseEventParser.cs | 46 +++- Application/IO/GameLogEventDetection.cs | 2 +- Application/IW4MServer.cs | 105 ++++---- Application/RconParsers/BaseRConParser.cs | 14 +- Plugins/Stats/Plugin.cs | 22 ++ SharedLibraryCore/Commands/NativeCommands.cs | 2 +- SharedLibraryCore/Database/DatabaseContext.cs | 29 ++- SharedLibraryCore/Objects/EFClient.cs | 44 ++-- SharedLibraryCore/Server.cs | 12 +- .../Services/ChangeHistoryService.cs | 33 ++- SharedLibraryCore/Services/ClientService.cs | 237 +++++++++--------- SharedLibraryCore/Services/PenaltyService.cs | 113 ++------- SharedLibraryCore/Utilities.cs | 7 +- WebfrontCore/Views/Configuration/Index.cshtml | 2 +- 15 files changed, 358 insertions(+), 315 deletions(-) diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index a2d145e6f..a9c27d058 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -202,6 +202,11 @@ namespace IW4MAdmin.Application Logger.WriteWarning($"Failed to update status for {server}"); Logger.WriteDebug(e.GetExceptionInfo()); } + + finally + { + server.IsInitialized = true; + } })); } #if DEBUG diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index 8931021f8..de9ca73e0 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -139,11 +139,16 @@ namespace IW4MAdmin.Application.EventParsers if (match.Success) { - var origin = server.GetClientsAsList() - .First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong()); - var target = server.GetClientsAsList() - .First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong()); + string originId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].Value.ToString(); + string targetId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].Value.ToString(); + var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList() + .First(c => c.NetworkId == originId.ConvertLong()) : + Utilities.IW4MAdminClient(server); + + var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList() + .First(c => c.NetworkId == targetId.ConvertLong()) : + Utilities.IW4MAdminClient(server); return new GameEvent() { @@ -159,8 +164,14 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "ScriptKill") { - var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); - var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); + long originId = lineSplit[1].ConvertLong(); + long targetId = lineSplit[2].ConvertLong(); + + var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) : + server.GetClientsAsList().First(c => c.NetworkId == originId); + var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) : + server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server); + return new GameEvent() { Type = GameEvent.EventType.ScriptKill, @@ -173,8 +184,13 @@ namespace IW4MAdmin.Application.EventParsers if (eventType == "ScriptDamage") { - var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); - var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); + long originId = lineSplit[1].ConvertLong(); + long targetId = lineSplit[2].ConvertLong(); + + var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) : + server.GetClientsAsList().First(c => c.NetworkId == originId); + var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) : + server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server); return new GameEvent() { @@ -195,10 +211,16 @@ namespace IW4MAdmin.Application.EventParsers if (regexMatch.Success) { - var origin = server.GetClientsAsList() - .First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong()); - var target = server.GetClientsAsList() - .First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong()); + string originId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString(); + string targetId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString(); + + var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList() + .First(c => c.NetworkId == originId.ConvertLong()) : + Utilities.IW4MAdminClient(server); + + var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList() + .First(c => c.NetworkId == targetId.ConvertLong()) : + Utilities.IW4MAdminClient(server); return new GameEvent() { diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 7e7aec0bd..3af8d2930 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.IO { while (!Server.Manager.ShutdownRequested()) { - if ((Server.Manager as ApplicationManager).IsInitialized) + if (Server.IsInitialized) { try { diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index f8000731e..008597683 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -18,6 +18,7 @@ using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using static SharedLibraryCore.Database.Models.EFClient; namespace IW4MAdmin { @@ -101,9 +102,7 @@ namespace IW4MAdmin Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting..."); await client.OnDisconnect(); Clients[client.ClientNumber] = null; -#if DEBUG == true - Logger.WriteDebug($"End PreDisconnect for {client}"); -#endif + var e = new GameEvent() { Origin = client, @@ -183,9 +182,8 @@ namespace IW4MAdmin if (E.Type == GameEvent.EventType.ChangePermission) { var newPermission = (EFClient.Permission)E.Extra; - E.Target.Level = newPermission; - - if (!E.Target.IsPrivileged()) + + if (newPermission < Permission.Moderator) { // remove banned or demoted privileged user Manager.GetPrivilegedClients().Remove(E.Target.ClientId); @@ -196,7 +194,7 @@ namespace IW4MAdmin Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target; } - await Manager.GetClientService().Update(E.Target); + await Manager.GetClientService().UpdateLevel((Permission)E.Extra, E.Target, E.Origin); } else if (E.Type == GameEvent.EventType.PreConnect) @@ -207,6 +205,9 @@ namespace IW4MAdmin return false; } + var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin)); + + CONNECT: if (Clients[E.Origin.ClientNumber] == null) { #if DEBUG == true @@ -227,6 +228,14 @@ namespace IW4MAdmin } } + // for some reason there's still a client in the spot + else if (existingClient == null) + { + Logger.WriteWarning($"{E.Origin} is connecteding but {Clients[E.Origin.ClientNumber]} is currently in that client slot"); + await OnClientDisconnected(Clients[E.Origin.ClientNumber]); + goto CONNECT; + } + else { return false; @@ -248,17 +257,17 @@ namespace IW4MAdmin }; var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty); - await Manager.GetClientService().Update(E.Target); + E.Target.SetLevel(Permission.Flagged, E.Origin); } else if (E.Type == GameEvent.EventType.Unflag) { - await Manager.GetClientService().Update(E.Target); + E.Target.SetLevel(Permission.User, E.Origin); } else if (E.Type == GameEvent.EventType.Report) { - this.Reports.Add(new Report() + Reports.Add(new Report() { Origin = E.Origin, Target = E.Target, @@ -292,42 +301,49 @@ namespace IW4MAdmin await Warn(E.Data, E.Target, E.Origin); } - else if (E.Type == GameEvent.EventType.Quit) - { - var origin = GetClientsAsList().FirstOrDefault(_client => _client.NetworkId.Equals(E.Origin)); + //else if (E.Type == GameEvent.EventType.Quit) + //{ + // var origin = GetClientsAsList().FirstOrDefault(_client => _client.NetworkId.Equals(E.Origin)); - if (origin != null) - { - var e = new GameEvent() - { - Type = GameEvent.EventType.Disconnect, - Origin = origin, - Owner = this - }; + // if (origin != null) + // { + // var e = new GameEvent() + // { + // Type = GameEvent.EventType.Disconnect, + // Origin = origin, + // Owner = this + // }; - Manager.GetEventHandler().AddEvent(e); - } + // Manager.GetEventHandler().AddEvent(e); + // } - else - { - return false; - } - } + // else + // { + // return false; + // } + //} else if (E.Type == GameEvent.EventType.Disconnect) { + ChatHistory.Add(new ChatInfo() + { + Name = E.Origin.Name, + Message = "DISCONNECTED", + Time = DateTime.UtcNow + }); + await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin); await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin); } else if (E.Type == GameEvent.EventType.PreDisconnect) { - if ((DateTime.UtcNow - SessionStart).TotalSeconds < 30) - { - Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end"); - E.FailReason = GameEvent.EventFailReason.Invalid; - return false; - } + //if ((DateTime.UtcNow - SessionStart).TotalSeconds < 30) + //{ + // Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end"); + // E.FailReason = GameEvent.EventFailReason.Invalid; + // return false; + //} // predisconnect comes from minimal rcon polled players and minimal log players // so we need to disconnect the "full" version of the client @@ -338,18 +354,16 @@ namespace IW4MAdmin #if DEBUG == true Logger.WriteDebug($"Begin PreDisconnect for {client}"); #endif - ChatHistory.Add(new ChatInfo() - { - Name = client.Name, - Message = "DISCONNECTED", - Time = DateTime.UtcNow - }); - await OnClientDisconnected(client); +#if DEBUG == true + Logger.WriteDebug($"End PreDisconnect for {client}"); +#endif } else { + Logger.WriteDebug($"Client {E.Origin} detected as disconnecting, but could not find them in the player list"); + Logger.WriteDebug($"Expected {E.Origin} but found {GetClientsAsList().FirstOrDefault(_client => _client.ClientNumber == E.Origin.ClientNumber)}"); return false; } } @@ -410,7 +424,7 @@ namespace IW4MAdmin var dict = (Dictionary)E.Extra; Gametype = dict["g_gametype"].StripColors(); Hostname = dict["sv_hostname"].StripColors(); - MaxClients = Int32.Parse(dict["sv_maxclients"]); + MaxClients = int.Parse(dict["sv_maxclients"]); string mapname = dict["mapname"].StripColors(); CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() @@ -493,6 +507,7 @@ namespace IW4MAdmin #endif var currentClients = GetClientsAsList(); var polledClients = (await this.GetStatusAsync()).AsEnumerable(); + if (Manager.GetApplicationSettings().Configuration().IgnoreBots) { polledClients = polledClients.Where(c => !c.IsBot); @@ -970,8 +985,6 @@ namespace IW4MAdmin else { - // this is set only because they're still in the server. - targetClient.Level = EFClient.Permission.Banned; #if !DEBUG string formattedString = String.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7"); @@ -994,6 +1007,7 @@ namespace IW4MAdmin }; await Manager.GetPenaltyService().Create(newPenalty); + targetClient.SetLevel(Permission.Banned, originClient); } override public async Task Unban(string reason, EFClient Target, EFClient Origin) @@ -1010,8 +1024,9 @@ namespace IW4MAdmin Link = Target.AliasLink }; - await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId); + await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId, Origin); await Manager.GetPenaltyService().Create(unbanPenalty); + Target.SetLevel(Permission.User, Origin); } override public void InitializeTokens() diff --git a/Application/RconParsers/BaseRConParser.cs b/Application/RconParsers/BaseRConParser.cs index e2688de5b..23647c00a 100644 --- a/Application/RconParsers/BaseRConParser.cs +++ b/Application/RconParsers/BaseRConParser.cs @@ -119,9 +119,9 @@ namespace IW4MAdmin.Application.RconParsers } int validMatches = 0; - foreach (string S in Status) + foreach (string statusLine in Status) { - string responseLine = S.Trim(); + string responseLine = statusLine.Trim(); var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase); @@ -158,11 +158,11 @@ namespace IW4MAdmin.Application.RconParsers State = EFClient.ClientState.Connecting }; - // they've not fully connected yet - if (!client.IsBot && ping == 999) - { - continue; - } + //// they've not fully connected yet + //if (!client.IsBot && ping == 999) + //{ + // continue; + //} StatusPlayers.Add(client); } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 0a9722827..89e3237c8 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -86,12 +86,34 @@ namespace IW4MAdmin.Plugins.Stats case GameEvent.EventType.Kill: if (!E.Owner.CustomCallback) { + // this treats "world" damage as self damage + if (E.Origin.ClientId == 1) + { + E.Origin = E.Target; + } + + if (E.Target.ClientId == 1) + { + E.Target = E.Origin; + } + await Manager.AddStandardKill(E.Origin, E.Target); } break; case GameEvent.EventType.Damage: if (!E.Owner.CustomCallback) { + // this treats "world" damage as self damage + if (E.Origin.ClientId == 1) + { + E.Origin = E.Target; + } + + if (E.Target.ClientId == 1) + { + E.Target = E.Origin; + } + Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner)); } break; diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index 3417cc6f6..6fccacb9a 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -1084,7 +1084,7 @@ namespace SharedLibraryCore.Commands .Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator) .Where(c => c.LastConnection < lastActive) .ToListAsync(); - inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User); + inactiveUsers.ForEach(c => c.SetLevel(EFClient.Permission.User, E.Origin)); await context.SaveChangesAsync(); } E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}"); diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index e60bbd806..b55876779 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -24,12 +24,33 @@ namespace SharedLibraryCore.Database static string _ConnectionString; static string _provider; private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins"; + private static int activeContextCount; - public DatabaseContext(DbContextOptions opt) : base(opt) { } + public DatabaseContext(DbContextOptions opt) : base(opt) + { +#if DEBUG == true + activeContextCount++; + Console.WriteLine($"Initialized DB Context #{activeContextCount}"); +#endif + } - public DatabaseContext() { } + public DatabaseContext() + { +#if DEBUG == true + activeContextCount++; + Console.WriteLine($"Initialized DB Context #{activeContextCount}"); +#endif + } - public DatabaseContext(bool disableTracking) + public override void Dispose() + { +#if DEBUG == true + activeContextCount--; + Console.WriteLine($"Disposed DB Context #{activeContextCount}"); +#endif + } + + public DatabaseContext(bool disableTracking) : this() { if (disableTracking) { @@ -46,7 +67,7 @@ namespace SharedLibraryCore.Database } } - public DatabaseContext(string connStr, string provider) + public DatabaseContext(string connStr, string provider) : this() { _ConnectionString = connStr; _provider = provider; diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index 032740a25..1ab90a312 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -81,6 +81,7 @@ namespace SharedLibraryCore.Database.Models { "_reportCount", 0 } }; CurrentAlias = new EFAlias(); + ReceivedPenalties = new List(); } public override string ToString() @@ -242,11 +243,6 @@ namespace SharedLibraryCore.Database.Models e.FailReason = GameEvent.EventFailReason.Invalid; } - else - { - this.Level = Permission.Flagged; - } - sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -274,16 +270,11 @@ namespace SharedLibraryCore.Database.Models e.FailReason = GameEvent.EventFailReason.Permission; } - else if (this.Level != EFClient.Permission.Flagged) + else if (this.Level != Permission.Flagged) { e.FailReason = GameEvent.EventFailReason.Invalid; } - else - { - this.Level = Permission.User; - } - sender.CurrentServer.Manager.GetEventHandler().AddEvent(e); return e; } @@ -403,15 +394,15 @@ namespace SharedLibraryCore.Database.Models /// /// sets the level of the client /// - /// new permission to set client to + /// new permission to set client to /// user performing the set level /// - public GameEvent SetLevel(Permission permission, EFClient sender) + public GameEvent SetLevel(Permission newPermission, EFClient sender) { var e = new GameEvent() { Type = GameEvent.EventType.ChangePermission, - Extra = permission, + Extra = newPermission, Origin = sender, Target = this, Owner = sender.CurrentServer @@ -493,19 +484,22 @@ namespace SharedLibraryCore.Database.Models // we want to run any non GUID based logic here OnConnect(); - if (await CanConnect(ipAddress) && IPAddress != null) + if (await CanConnect(ipAddress)) { - var e = new GameEvent() + if (IPAddress != null) { - Type = GameEvent.EventType.Join, - Origin = this, - Target = this, - Owner = CurrentServer - }; + await CurrentServer.Manager.GetClientService().Update(this); - CurrentServer.Manager.GetEventHandler().AddEvent(e); + var e = new GameEvent() + { + Type = GameEvent.EventType.Join, + Origin = this, + Target = this, + Owner = CurrentServer + }; - await CurrentServer.Manager.GetClientService().Update(this); + CurrentServer.Manager.GetEventHandler().AddEvent(e); + } } else @@ -525,7 +519,6 @@ namespace SharedLibraryCore.Database.Models // kick them as their level is banned if (Level == Permission.Banned) { - CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned"); var profileBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Expires == null && _penalty.Active); if (profileBan == null) @@ -558,6 +551,7 @@ namespace SharedLibraryCore.Database.Models return false; } + CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned"); Kick($"{loc["SERVER_BAN_PREV"]} {profileBan?.Offense}", autoKickClient); return false; } @@ -566,7 +560,7 @@ namespace SharedLibraryCore.Database.Models #region CLIENT_GUID_TEMPBAN else { - var profileTempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan && + var profileTempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan && _penalty.Active && _penalty.Expires > DateTime.UtcNow); diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 18f80281d..68102fa99 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -87,6 +87,11 @@ namespace SharedLibraryCore /// Matching player if found public List GetClientByName(String pName) { + if (string.IsNullOrEmpty(pName)) + { + return new List(); + } + string[] QuoteSplit = pName.Split('"'); bool literal = false; if (QuoteSplit.Length > 1) @@ -95,9 +100,11 @@ namespace SharedLibraryCore literal = true; } if (literal) - return Clients.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList(); + { + return GetClientsAsList().Where(p => p.Name?.ToLower() == pName.ToLower()).ToList(); + } - return Clients.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList(); + return GetClientsAsList().Where(p => (p.Name?.ToLower() ?? "").Contains(pName.ToLower())).ToList(); } virtual public Task ProcessUpdatesAsync(CancellationToken cts) => (Task)Task.CompletedTask; @@ -313,6 +320,7 @@ namespace SharedLibraryCore // Internal public string IP { get; protected set; } public string Version { get; protected set; } + public bool IsInitialized { get; set; } protected int Port; protected string FSGame; diff --git a/SharedLibraryCore/Services/ChangeHistoryService.cs b/SharedLibraryCore/Services/ChangeHistoryService.cs index 808c0cf18..b540c008a 100644 --- a/SharedLibraryCore/Services/ChangeHistoryService.cs +++ b/SharedLibraryCore/Services/ChangeHistoryService.cs @@ -1,6 +1,5 @@ using SharedLibraryCore.Database; using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Events; using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; @@ -16,7 +15,7 @@ namespace SharedLibraryCore.Services throw new NotImplementedException(); } - public async Task Add(GameEvent e) + public async Task Add(GameEvent e, DatabaseContext ctx = null) { EFChangeHistory change = null; @@ -54,6 +53,7 @@ namespace SharedLibraryCore.Services { OriginEntityId = e.Origin.ClientId, TargetEntityId = e.Target.ClientId, + Comment = "Changed permission level", TypeOfChange = EFChangeHistory.ChangeType.Permission, CurrentValue = ((EFClient.Permission)e.Extra).ToString() }; @@ -64,18 +64,27 @@ namespace SharedLibraryCore.Services if (change != null) { - using (var ctx = new DatabaseContext(true)) - { - ctx.EFChangeHistory.Add(change); - try - { - await ctx.SaveChangesAsync(); - } + bool existingCtx = ctx != null; + ctx = ctx ?? new DatabaseContext(true); - catch (Exception ex) + ctx.EFChangeHistory.Add(change); + + try + { + await ctx.SaveChangesAsync(); + } + + catch (Exception ex) + { + e.Owner.Logger.WriteWarning(ex.Message); + e.Owner.Logger.WriteDebug(ex.GetExceptionInfo()); + } + + finally + { + if (!existingCtx) { - e.Owner.Logger.WriteWarning(ex.Message); - e.Owner.Logger.WriteDebug(ex.GetExceptionInfo()); + ctx.Dispose(); } } } diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index 19a25ec1c..9e439736b 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -22,13 +22,8 @@ namespace SharedLibraryCore.Services Level = Permission.User, FirstConnection = DateTime.UtcNow, LastConnection = DateTime.UtcNow, - Masked = false, NetworkId = entity.NetworkId, AliasLink = new EFAliasLink() - { - Active = false - }, - ReceivedPenalties = new List() }; client.CurrentAlias = new Alias() @@ -37,9 +32,6 @@ namespace SharedLibraryCore.Services Link = client.AliasLink, DateAdded = DateTime.UtcNow, IPAddress = entity.IPAddress, - // the first time a client is created, we may not have their ip, - // so we create a temporary alias - Active = false }; context.Clients.Add(client); @@ -55,8 +47,8 @@ namespace SharedLibraryCore.Services // get all aliases by IP address and LinkId var iqAliases = context.Aliases .Include(a => a.Link) - .Where(a => (a.IPAddress == ip) || - a.LinkId == entity.AliasLinkId); + // we only want alias that have the same IP address or share a link + .Where(_alias => _alias.IPAddress == ip || (_alias.LinkId == entity.AliasLinkId && _alias.Active)); #if DEBUG == true var aliasSql = iqAliases.ToSql(); @@ -65,75 +57,66 @@ namespace SharedLibraryCore.Services // see if they have a matching IP + Name but new NetworkId var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip); - bool exactAliasMatch = existingExactAlias != null; + bool hasExactAliasMatch = existingExactAlias != null; // if existing alias matches link them - EFAliasLink aliasLink = existingExactAlias?.Link; - // if no exact matches find the first IP that matches - aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link; - // if no matches are found, use our current one - aliasLink = aliasLink ?? entity.AliasLink; + var newAliasLink = existingExactAlias?.Link; + // if no exact matches find the first IP or LinkId that matches + newAliasLink = newAliasLink ?? aliases.FirstOrDefault()?.Link; + // if no matches are found, use our current one ( it will become permanent ) + newAliasLink = newAliasLink ?? entity.AliasLink; bool hasExistingAlias = aliases.Count > 0; + bool isAliasLinkUpdated = newAliasLink.AliasLinkId != entity.AliasLink.AliasLinkId; - // this happens when an alias exists but the current link is a temporary one - if ((exactAliasMatch || hasExistingAlias) && - (!entity.AliasLink.Active && entity.AliasLinkId != aliasLink.AliasLinkId)) + // this happens when the link we found is different than the one we create before adding an IP + if (isAliasLinkUpdated) { - entity.AliasLinkId = aliasLink.AliasLinkId; - entity.AliasLink = aliasLink; + entity.CurrentServer.Logger.WriteDebug($"found a link for {entity} so we are updating link from {entity.AliasLink.AliasLinkId} to {newAliasLink.AliasLinkId}"); + + var oldAliasLink = entity.AliasLink; + + entity.AliasLink = newAliasLink; + entity.AliasLinkId = newAliasLink.AliasLinkId; + + // update all previous aliases + await context.Aliases + .Where(_alias => _alias.LinkId == oldAliasLink.AliasLinkId) + .ForEachAsync(_alias => { _alias.LinkId = newAliasLink.AliasLinkId; _alias.Active = true; }); - //entity.CurrentServer.Logger.WriteDebug($"Updating alias link for {entity}"); await context.SaveChangesAsync(); - - foreach (var alias in aliases.Append(entity.CurrentAlias) - .Where(_alias => !_alias.Active || - _alias.LinkId != aliasLink.AliasLinkId)) - { - entity.CurrentServer.Logger.WriteDebug($"{entity} updating alias-link id is {alias.LinkId}"); - alias.Active = true; - alias.LinkId = aliasLink.AliasLinkId; - } - - //entity.CurrentServer.Logger.WriteDebug($"Saving updated aliases for {entity}"); + // we want to delete the now inactive alias + context.AliasLinks.Remove(oldAliasLink); await context.SaveChangesAsync(); - - // todo: fix this - /*context.AliasLinks.Remove(entity.AliasLink); - entity.AliasLink = null; - - //entity.CurrentServer.Logger.WriteDebug($"Removing temporary link for {entity}"); - - try - { - await context.SaveChangesAsync(); - } - catch - { - // entity.CurrentServer.Logger.WriteDebug($"Failed to remove link for {entity}"); - }*/ } // the existing alias matches ip and name, so we can just ignore the temporary one - if (exactAliasMatch) + if (hasExactAliasMatch) { entity.CurrentServer.Logger.WriteDebug($"{entity} has exact alias match"); + + var oldAlias = entity.CurrentAlias; entity.CurrentAliasId = existingExactAlias.AliasId; entity.CurrentAlias = existingExactAlias; await context.SaveChangesAsync(); + + // the alias is the same so we can just remove it + if (oldAlias.AliasId != existingExactAlias.AliasId) + { + context.Aliases.Remove(oldAlias); + await context.SaveChangesAsync(); + } } // theres no exact match, but they've played before with the GUID or IP else if (hasExistingAlias) { - //entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}"); + entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}"); // this happens when a temporary alias gets updated if (entity.CurrentAlias.Name == name && entity.CurrentAlias.IPAddress == null) { entity.CurrentAlias.IPAddress = ip; - entity.CurrentAlias.Active = true; - //entity.CurrentServer.Logger.WriteDebug($"Updating temporary alias for {entity}"); await context.SaveChangesAsync(); } @@ -143,13 +126,12 @@ namespace SharedLibraryCore.Services { DateAdded = DateTime.UtcNow, IPAddress = ip, - LinkId = aliasLink.AliasLinkId, + LinkId = newAliasLink.AliasLinkId, Name = name, Active = true, }; entity.CurrentAlias = newAlias; - //entity.CurrentServer.Logger.WriteDebug($"Saving new alias for {entity}"); await context.SaveChangesAsync(); } } @@ -157,35 +139,87 @@ namespace SharedLibraryCore.Services // no record of them playing else { - //entity.CurrentServer.Logger.WriteDebug($"{entity} has not be seen before"); - entity.AliasLink.Active = true; entity.CurrentAlias.Active = true; entity.CurrentAlias.IPAddress = ip; entity.CurrentAlias.Name = name; - //entity.CurrentServer.Logger.WriteDebug($"updating new alias for {entity}"); await context.SaveChangesAsync(); } - var linkIds = aliases.Select(a => a.LinkId); + //var linkIds = aliases.Select(a => a.LinkId); - if (linkIds.Count() > 0 && - aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0) + //if (linkIds.Count() > 0 && + // aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0) + //{ + // var highestLevel = await context.Clients + // .Where(c => linkIds.Contains(c.AliasLinkId)) + // .MaxAsync(c => c.Level); + + // if (entity.Level != highestLevel) + // { + // entity.CurrentServer.Logger.WriteDebug($"{entity} updating user level"); + // // todo: log level changes here + // context.Update(entity); + // entity.SetLevel(highestLevel, Utilities.IW4MAdminClient(entity.CurrentServer)); + // await context.SaveChangesAsync(); + // } + //} + } + + /// + /// updates the permission level of the given target to the given permission level + /// + /// + /// + /// + /// + /// + public async Task UpdateLevel(Permission newPermission, EFClient temporalClient, EFClient origin) + { + using (var ctx = new DatabaseContext()) { - var highestLevel = await context.Clients - .Where(c => linkIds.Contains(c.AliasLinkId)) - .MaxAsync(c => c.Level); + var entity = await ctx.Clients + .Where(_client => _client.AliasLinkId == temporalClient.AliasLinkId) + .FirstAsync(); - if (entity.Level != highestLevel) + // if their permission level has been changed to level that needs to be updated on all accounts + if ((entity.Level != newPermission) && + (newPermission == Permission.Banned || + newPermission == Permission.Flagged || + newPermission == Permission.User)) { - entity.CurrentServer.Logger.WriteDebug($"{entity} updating user level"); - // todo: log level changes here - context.Update(entity); - entity.Level = highestLevel; - await context.SaveChangesAsync(); + var changeSvc = new ChangeHistoryService(); + + // get all clients that have the same linkId + var iqMatchingClients = ctx.Clients + .Where(_client => _client.AliasLinkId == entity.AliasLinkId) + // make sure we don't select ourselves twice + .Where(_client => _client.ClientId != temporalClient.ClientId); + + var matchingClients = await iqMatchingClients.ToListAsync(); + + // this updates the level for all the clients with the same LinkId + // only if their new level is flagged or banned + foreach (var client in matchingClients) + { + client.Level = newPermission; + // hack this saves our change to the change history log + await changeSvc.Add(new GameEvent() + { + Type = GameEvent.EventType.ChangePermission, + Extra = newPermission, + Origin = origin, + Target = client + }, ctx); + } } + + entity.Level = newPermission; + await ctx.SaveChangesAsync(); } + + temporalClient.Level = newPermission; } public async Task Delete(EFClient entity) @@ -274,73 +308,52 @@ namespace SharedLibraryCore.Services } } - public async Task UpdateAlias(EFClient entity) + public async Task UpdateAlias(EFClient temporalClient) { using (var context = new DatabaseContext()) { - var client = context.Clients + var entity = context.Clients .Include(c => c.AliasLink) .Include(c => c.CurrentAlias) - .First(e => e.ClientId == entity.ClientId); + .First(e => e.ClientId == temporalClient.ClientId); - client.CurrentServer = entity.CurrentServer; + entity.CurrentServer = temporalClient.CurrentServer; - await UpdateAlias(entity.Name, entity.IPAddress, client, context); + await UpdateAlias(temporalClient.Name, temporalClient.IPAddress, entity, context); - entity.CurrentAlias = client.CurrentAlias; - entity.CurrentAliasId = client.CurrentAliasId; - entity.AliasLink = client.AliasLink; - entity.AliasLinkId = client.AliasLinkId; + temporalClient.CurrentAlias = entity.CurrentAlias; + temporalClient.CurrentAliasId = entity.CurrentAliasId; + temporalClient.AliasLink = entity.AliasLink; + temporalClient.AliasLinkId = entity.AliasLinkId; } } - public async Task Update(EFClient entity) + public async Task Update(EFClient temporalClient) { using (var context = new DatabaseContext()) { // grab the context version of the entity - var client = context.Clients - .First(e => e.ClientId == entity.ClientId); + var entity = context.Clients + .First(client => client.ClientId == temporalClient.ClientId); - client.CurrentServer = entity.CurrentServer; - - // if their level has been changed - if (entity.Level != client.Level) - { - // get all clients that use the same aliasId - var matchingClients = context.Clients - .Where(c => c.CurrentAliasId == client.CurrentAliasId) - // make sure we don't select ourselves twice - .Where(c => c.ClientId != entity.ClientId); - - // update all related clients level - await matchingClients.ForEachAsync(c => - { - // todo: log that it has changed here - c.Level = entity.Level; - }); - } - - // set remaining non-navigation properties that may have been updated - client.Level = entity.Level; - client.LastConnection = entity.LastConnection; - client.Connections = entity.Connections; - client.FirstConnection = entity.FirstConnection; - client.Masked = entity.Masked; - client.TotalConnectionTime = entity.TotalConnectionTime; - client.Password = entity.Password; - client.PasswordSalt = entity.PasswordSalt; + entity.LastConnection = temporalClient.LastConnection; + entity.Connections = temporalClient.Connections; + entity.FirstConnection = temporalClient.FirstConnection; + entity.Masked = temporalClient.Masked; + entity.TotalConnectionTime = temporalClient.TotalConnectionTime; + entity.Password = temporalClient.Password; + entity.PasswordSalt = temporalClient.PasswordSalt; // update in database await context.SaveChangesAsync(); // this is set so future updates don't trigger a new alias add - if (entity.CurrentAlias.AliasId == 0) + if (temporalClient.CurrentAlias.AliasId == 0) { - entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId; + temporalClient.CurrentAlias.AliasId = entity.CurrentAlias.AliasId; } - return client; + return entity; } } diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index b83904225..52fe6068e 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -18,86 +18,26 @@ namespace SharedLibraryCore.Services { using (var context = new DatabaseContext()) { - // make bans propogate to all aliases - if (newEntity.Type == Penalty.PenaltyType.Ban) + var penalty = new EFPenalty() { - await context.Clients - .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 (newEntity.Type == Penalty.PenaltyType.Flag) - { - await context.Clients - .Include(c => c.ReceivedPenalties) - .Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId) - .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 - { - var penalty = 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 - }; - - newEntity.Offender.ReceivedPenalties?.Add(penalty); - context.Penalties.Add(penalty); - } + 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 + }; + newEntity.Offender.ReceivedPenalties?.Add(penalty); + context.Penalties.Add(penalty); await context.SaveChangesAsync(); - return newEntity; } + + return newEntity; } public Task CreateProxy() @@ -257,31 +197,20 @@ namespace SharedLibraryCore.Services } } - public async Task RemoveActivePenalties(int aliasLinkId) + public async Task RemoveActivePenalties(int aliasLinkId, EFClient origin) { using (var context = new DatabaseContext()) { var now = DateTime.UtcNow; - var penalties = await context.Penalties + var penalties = context.Penalties .Include(p => p.Link.Children) .Where(p => p.LinkId == aliasLinkId) - .Where(p => p.Expires > now || p.Expires == null) - .ToListAsync(); + .Where(p => p.Expires > now || p.Expires == null); - penalties.ForEach(async p => + await penalties.ForEachAsync(p => { p.Active = false; - // reset the player levels - if (p.Type == Penalty.PenaltyType.Ban) - { - using (var internalContext = new DatabaseContext()) - { - await internalContext.Clients - .Where(c => c.AliasLinkId == p.LinkId) - .ForEachAsync(c => c.Level = EFClient.Permission.User); - await internalContext.SaveChangesAsync(); - } - } + }); await context.SaveChangesAsync(); diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index a0f5d5bb2..116460541 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -478,9 +478,14 @@ namespace SharedLibraryCore return "unknown"; } + /// + /// Helper extension that determines if a user is a privileged client + /// + /// + /// public static bool IsPrivileged(this EFClient p) { - return p.Level > EFClient.Permission.User; + return p.Level > EFClient.Permission.Flagged; } /// diff --git a/WebfrontCore/Views/Configuration/Index.cshtml b/WebfrontCore/Views/Configuration/Index.cshtml index 796b568e2..2c053f3ed 100644 --- a/WebfrontCore/Views/Configuration/Index.cshtml +++ b/WebfrontCore/Views/Configuration/Index.cshtml @@ -80,7 +80,7 @@