From feb38656e8dcb8214767f0a3129457e1e17671f3 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 16 Feb 2019 15:04:40 -0600 Subject: [PATCH] fix damage event not including log line complete initiall implementation for "2FA" issue #52 issue #66 --- Application/Application.csproj | 2 +- Application/ApplicationManager.cs | 2 +- Application/EventParsers/BaseEventParser.cs | 4 +-- Application/Misc/TokenAuthentication.cs | 36 +++++++++++-------- Plugins/Login/Commands/CLogin.cs | 21 +++++------ Plugins/Stats/Helpers/StatManager.cs | 25 ++++++------- .../Commands/GenerateTokenCommand.cs | 22 ------------ .../Commands/RequestTokenCommand.cs | 20 +++++++++++ SharedLibraryCore/Helpers/TokenState.cs | 15 ++++++++ .../Interfaces/ITokenAuthentication.cs | 5 +-- WebfrontCore/Controllers/AccountController.cs | 13 +++---- WebfrontCore/Controllers/ActionController.cs | 21 +++++++++++ WebfrontCore/Views/Shared/_Layout.cshtml | 1 + .../Shared/_ValidationScriptsPartial.cshtml | 18 ---------- WebfrontCore/WebfrontCore.csproj | 6 ++-- WebfrontCore/wwwroot/css/bootstrap-custom.css | 1 - WebfrontCore/wwwroot/css/profile.css | 1 - 17 files changed, 115 insertions(+), 98 deletions(-) delete mode 100644 SharedLibraryCore/Commands/GenerateTokenCommand.cs create mode 100644 SharedLibraryCore/Commands/RequestTokenCommand.cs create mode 100644 SharedLibraryCore/Helpers/TokenState.cs delete mode 100644 WebfrontCore/Views/Shared/_ValidationScriptsPartial.cshtml diff --git a/Application/Application.csproj b/Application/Application.csproj index 5cd12b65..7e478c63 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -10,7 +10,7 @@ RaidMax Forever None IW4MAdmin - IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server + IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers 2019 https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE https://raidmax.org/IW4MAdmin diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index da982303..4cae4cb5 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -376,7 +376,7 @@ namespace IW4MAdmin.Application Commands.Add(new CPing()); Commands.Add(new CSetGravatar()); Commands.Add(new CNextMap()); - Commands.Add(new GenerateTokenCommand()); + Commands.Add(new RequestTokenCommand()); foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) { diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index 5ded70a4..8931021f 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -87,7 +87,7 @@ namespace IW4MAdmin.Application.EventParsers return new GameEvent() { Type = GameEvent.EventType.JoinTeam, - Data = eventType, + Data = logLine, Origin = origin, Owner = server }; @@ -203,7 +203,7 @@ namespace IW4MAdmin.Application.EventParsers return new GameEvent() { Type = GameEvent.EventType.Damage, - Data = eventType, + Data = logLine, Origin = origin, Target = target, Owner = server diff --git a/Application/Misc/TokenAuthentication.cs b/Application/Misc/TokenAuthentication.cs index 41249d79..1fbe9957 100644 --- a/Application/Misc/TokenAuthentication.cs +++ b/Application/Misc/TokenAuthentication.cs @@ -1,4 +1,5 @@ -using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Helpers; +using SharedLibraryCore.Interfaces; using System; using System.Collections.Concurrent; using System.Security.Cryptography; @@ -10,16 +11,9 @@ namespace IW4MAdmin.Application.Misc { private readonly ConcurrentDictionary _tokens; private readonly RNGCryptoServiceProvider _random; - private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 30); + private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 120); private const short TOKEN_LENGTH = 4; - private class TokenState - { - public long NetworkId { get; set; } - public DateTime RequestTime { get; set; } = DateTime.Now; - public string Token { get; set; } - } - public TokenAuthentication() { _tokens = new ConcurrentDictionary(); @@ -38,32 +32,44 @@ namespace IW4MAdmin.Application.Misc return authorizeSuccessful; } - public string GenerateNextToken(long networkId) + public TokenState GenerateNextToken(long networkId) { TokenState state = null; + if (_tokens.ContainsKey(networkId)) { state = _tokens[networkId]; - if ((DateTime.Now - state.RequestTime) < _timeoutPeriod) + if ((DateTime.Now - state.RequestTime) > _timeoutPeriod) { - return null; + _tokens.TryRemove(networkId, out TokenState _); } else { - _tokens.TryRemove(networkId, out TokenState _); + return state; } } state = new TokenState() { NetworkId = networkId, - Token = _generateToken() + Token = _generateToken(), + TokenDuration = _timeoutPeriod }; _tokens.TryAdd(networkId, state); - return state.Token; + + // perform some housekeeping so we don't have built up tokens if they're not ever used + foreach (var (key, value) in _tokens) + { + if ((DateTime.Now - value.RequestTime) > _timeoutPeriod) + { + _tokens.TryRemove(key, out TokenState _); + } + } + + return state; } public string _generateToken() diff --git a/Plugins/Login/Commands/CLogin.cs b/Plugins/Login/Commands/CLogin.cs index 509434ef..d92385f9 100644 --- a/Plugins/Login/Commands/CLogin.cs +++ b/Plugins/Login/Commands/CLogin.cs @@ -1,8 +1,5 @@ using SharedLibraryCore; using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Objects; -using System; -using System.Collections.Generic; using System.Threading.Tasks; namespace IW4MAdmin.Plugins.Login.Commands @@ -22,18 +19,22 @@ namespace IW4MAdmin.Plugins.Login.Commands public override async Task ExecuteAsync(GameEvent E) { var client = E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId]; - string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt)); + bool success = E.Owner.Manager.TokenAuthenticator.AuthorizeToken(E.Origin.NetworkId, E.Data); - if (hashedPassword[0] == client.Password) + if (!success) { - Plugin.AuthorizedClients[E.Origin.ClientId] = true; - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]); + string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt)); + + if (hashedPassword[0] == client.Password) + { + success = true; + Plugin.AuthorizedClients[E.Origin.ClientId] = true; + } } - else - { + _ = success ? + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) : E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]); - } } } } diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index b72b8048..66e9a775 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -427,19 +427,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) { - string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; - var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); + // todo: maybe do something with this + //string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; + //var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); - if (match.Success) - { - // this gives us what team the player is on - var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; - var victimStats = Servers[serverId].PlayerStats[victimClientId]; - IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); - IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString()); - attackerStats.Team = attackerTeam; - victimStats.Team = victimTeam; - } + //if (match.Success) + //{ + // // this gives us what team the player is on + // var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; + // var victimStats = Servers[serverId].PlayerStats[victimClientId]; + // IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString(), true); + // IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString(), true); + // attackerStats.Team = attackerTeam; + // victimStats.Team = victimTeam; + //} } /// diff --git a/SharedLibraryCore/Commands/GenerateTokenCommand.cs b/SharedLibraryCore/Commands/GenerateTokenCommand.cs deleted file mode 100644 index 596837aa..00000000 --- a/SharedLibraryCore/Commands/GenerateTokenCommand.cs +++ /dev/null @@ -1,22 +0,0 @@ -using SharedLibraryCore.Database.Models; -using System.Threading.Tasks; - -namespace SharedLibraryCore.Commands -{ - public class GenerateTokenCommand : Command - { - public GenerateTokenCommand() : - base("generatetoken", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_DESC"], "gt", EFClient.Permission.Trusted, false) - { } - - public override Task ExecuteAsync(GameEvent E) - { - string token = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId); - var _event = token == null ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_FAIL"]) : - E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"]} {token}"); - - return Task.CompletedTask; - } -} -} diff --git a/SharedLibraryCore/Commands/RequestTokenCommand.cs b/SharedLibraryCore/Commands/RequestTokenCommand.cs new file mode 100644 index 00000000..6d0672ea --- /dev/null +++ b/SharedLibraryCore/Commands/RequestTokenCommand.cs @@ -0,0 +1,20 @@ +using SharedLibraryCore.Database.Models; +using System.Threading.Tasks; + +namespace SharedLibraryCore.Commands +{ + public class RequestTokenCommand : Command + { + public RequestTokenCommand() : + base("requesttoken", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_DESC"], "rt", EFClient.Permission.Trusted, false) + { } + + public override Task ExecuteAsync(GameEvent E) + { + var state = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId); + E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token, $"{state.RemainingTime} {Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_MINUTES"]}", E.Origin.ClientId)); + + return Task.CompletedTask; + } + } +} diff --git a/SharedLibraryCore/Helpers/TokenState.cs b/SharedLibraryCore/Helpers/TokenState.cs new file mode 100644 index 00000000..c5f8afb8 --- /dev/null +++ b/SharedLibraryCore/Helpers/TokenState.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SharedLibraryCore.Helpers +{ + public sealed class TokenState + { + public long NetworkId { get; set; } + public DateTime RequestTime { get; set; } = DateTime.Now; + public TimeSpan TokenDuration { get; set; } + public string Token { get; set; } + public string RemainingTime => Math.Round(-(DateTime.Now - RequestTime).Subtract(TokenDuration).TotalMinutes, 1).ToString(); + } +} diff --git a/SharedLibraryCore/Interfaces/ITokenAuthentication.cs b/SharedLibraryCore/Interfaces/ITokenAuthentication.cs index f9752862..58d0ce3d 100644 --- a/SharedLibraryCore/Interfaces/ITokenAuthentication.cs +++ b/SharedLibraryCore/Interfaces/ITokenAuthentication.cs @@ -1,4 +1,5 @@ -using System; +using SharedLibraryCore.Helpers; +using System; using System.Collections.Generic; using System.Text; @@ -11,7 +12,7 @@ namespace SharedLibraryCore.Interfaces /// /// network id of the players to generate the token for /// 4 character string token - string GenerateNextToken(long networkId); + TokenState GenerateNextToken(long networkId); /// /// authorizes given token diff --git a/WebfrontCore/Controllers/AccountController.cs b/WebfrontCore/Controllers/AccountController.cs index 60d94732..5cfdb3c8 100644 --- a/WebfrontCore/Controllers/AccountController.cs +++ b/WebfrontCore/Controllers/AccountController.cs @@ -21,9 +21,11 @@ namespace WebfrontCore.Controllers try { var client = Manager.GetPrivilegedClients()[clientId]; - string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, client.PasswordSalt)); - if (hashedPassword[0] == client.Password) + // string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, client.PasswordSalt)); + //if (hashedPassword[0] == client.Password) + + if (Manager.TokenAuthenticator.AuthorizeToken(client.NetworkId, password)) { var claims = new[] { @@ -61,12 +63,5 @@ namespace WebfrontCore.Controllers await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } - - [HttpGet] - [Authorize] - public async Task GenerateLoginTokenAsync() - { - return Json(new { token = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId) }); - } } } diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 4a02388c..35b86cb9 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using WebfrontCore.ViewModels; @@ -182,5 +183,25 @@ namespace WebfrontCore.Controllers command = $"!setlevel @{targetId} {level}" })); } + + public async Task GenerateLoginTokenForm() + { + var info = new ActionInfo() + { + ActionButtonLabel = "Generate", + Name = "GenerateLoginToken", + Action = "GenerateLoginTokenAsync", + Inputs = new List() + }; + + return View("_ActionForm", info); + } + + [Authorize] + public string GenerateLoginTokenAsync() + { + var state = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId); + return string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token, $"{state.RemainingTime} {Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_MINUTES"]}", Client.ClientId); + } } } diff --git a/WebfrontCore/Views/Shared/_Layout.cshtml b/WebfrontCore/Views/Shared/_Layout.cshtml index 249b2322..384f8e31 100644 --- a/WebfrontCore/Views/Shared/_Layout.cshtml +++ b/WebfrontCore/Views/Shared/_Layout.cshtml @@ -70,6 +70,7 @@ @class = "dropdown-item bg-dark text-muted text-center text-md-left", title = "Logout of account" }) + diff --git a/WebfrontCore/Views/Shared/_ValidationScriptsPartial.cshtml b/WebfrontCore/Views/Shared/_ValidationScriptsPartial.cshtml deleted file mode 100644 index a699aafa..00000000 --- a/WebfrontCore/Views/Shared/_ValidationScriptsPartial.cshtml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index 56525595..2697ce1f 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -3,7 +3,7 @@ netcoreapp2.2 2.2.2 - true + false true true 2.6 @@ -12,7 +12,7 @@ RaidMax Forever None IW4MAdmin - IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server + IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers 2019 https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE https://raidmax.org/IW4MAdmin @@ -74,7 +74,5 @@ - - diff --git a/WebfrontCore/wwwroot/css/bootstrap-custom.css b/WebfrontCore/wwwroot/css/bootstrap-custom.css index 51596636..c7a3f49b 100644 --- a/WebfrontCore/wwwroot/css/bootstrap-custom.css +++ b/WebfrontCore/wwwroot/css/bootstrap-custom.css @@ -6416,4 +6416,3 @@ form *, select { position: fixed; bottom: 1em; right: 1em; } - diff --git a/WebfrontCore/wwwroot/css/profile.css b/WebfrontCore/wwwroot/css/profile.css index 43996e6a..858edb75 100644 --- a/WebfrontCore/wwwroot/css/profile.css +++ b/WebfrontCore/wwwroot/css/profile.css @@ -178,7 +178,6 @@ } .profile-action { - padding-left: 0.25em; cursor: pointer; }