fix damage event not including log line

complete initiall implementation for "2FA"
issue #52
issue #66
This commit is contained in:
RaidMax 2019-02-16 15:04:40 -06:00
parent 2bbf2988da
commit 40f1697c97
17 changed files with 115 additions and 98 deletions

View File

@ -10,7 +10,7 @@
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description> <Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
<Copyright>2019</Copyright> <Copyright>2019</Copyright>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl> <PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>

View File

@ -376,7 +376,7 @@ namespace IW4MAdmin.Application
Commands.Add(new CPing()); Commands.Add(new CPing());
Commands.Add(new CSetGravatar()); Commands.Add(new CSetGravatar());
Commands.Add(new CNextMap()); Commands.Add(new CNextMap());
Commands.Add(new GenerateTokenCommand()); Commands.Add(new RequestTokenCommand());
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands) foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
{ {

View File

@ -87,7 +87,7 @@ namespace IW4MAdmin.Application.EventParsers
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.JoinTeam, Type = GameEvent.EventType.JoinTeam,
Data = eventType, Data = logLine,
Origin = origin, Origin = origin,
Owner = server Owner = server
}; };
@ -203,7 +203,7 @@ namespace IW4MAdmin.Application.EventParsers
return new GameEvent() return new GameEvent()
{ {
Type = GameEvent.EventType.Damage, Type = GameEvent.EventType.Damage,
Data = eventType, Data = logLine,
Origin = origin, Origin = origin,
Target = target, Target = target,
Owner = server Owner = server

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -10,16 +11,9 @@ namespace IW4MAdmin.Application.Misc
{ {
private readonly ConcurrentDictionary<long, TokenState> _tokens; private readonly ConcurrentDictionary<long, TokenState> _tokens;
private readonly RNGCryptoServiceProvider _random; 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 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() public TokenAuthentication()
{ {
_tokens = new ConcurrentDictionary<long, TokenState>(); _tokens = new ConcurrentDictionary<long, TokenState>();
@ -38,32 +32,44 @@ namespace IW4MAdmin.Application.Misc
return authorizeSuccessful; return authorizeSuccessful;
} }
public string GenerateNextToken(long networkId) public TokenState GenerateNextToken(long networkId)
{ {
TokenState state = null; TokenState state = null;
if (_tokens.ContainsKey(networkId)) if (_tokens.ContainsKey(networkId))
{ {
state = _tokens[networkId]; state = _tokens[networkId];
if ((DateTime.Now - state.RequestTime) < _timeoutPeriod) if ((DateTime.Now - state.RequestTime) > _timeoutPeriod)
{ {
return null; _tokens.TryRemove(networkId, out TokenState _);
} }
else else
{ {
_tokens.TryRemove(networkId, out TokenState _); return state;
} }
} }
state = new TokenState() state = new TokenState()
{ {
NetworkId = networkId, NetworkId = networkId,
Token = _generateToken() Token = _generateToken(),
TokenDuration = _timeoutPeriod
}; };
_tokens.TryAdd(networkId, state); _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() public string _generateToken()

View File

@ -1,8 +1,5 @@
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models; using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IW4MAdmin.Plugins.Login.Commands namespace IW4MAdmin.Plugins.Login.Commands
@ -22,18 +19,22 @@ namespace IW4MAdmin.Plugins.Login.Commands
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
var client = E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId]; 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; string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt));
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
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"]); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
}
} }
} }
} }

View File

@ -427,19 +427,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId)
{ {
string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$"; // todo: maybe do something with this
var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase); //string regex = @"^(D);(.+);([0-9]+);(allies|axis);(.+);([0-9]+);(allies|axis);(.+);(.+);([0-9]+);(.+);(.+)$";
//var match = Regex.Match(eventLine, regex, RegexOptions.IgnoreCase);
if (match.Success) //if (match.Success)
{ //{
// this gives us what team the player is on // // this gives us what team the player is on
var attackerStats = Servers[serverId].PlayerStats[attackerClientId]; // var attackerStats = Servers[serverId].PlayerStats[attackerClientId];
var victimStats = Servers[serverId].PlayerStats[victimClientId]; // var victimStats = Servers[serverId].PlayerStats[victimClientId];
IW4Info.Team victimTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[4].ToString()); // 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()); // IW4Info.Team attackerTeam = (IW4Info.Team)Enum.Parse(typeof(IW4Info.Team), match.Groups[7].ToString(), true);
attackerStats.Team = attackerTeam; // attackerStats.Team = attackerTeam;
victimStats.Team = victimTeam; // victimStats.Team = victimTeam;
} //}
} }
/// <summary> /// <summary>

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -1,4 +1,5 @@
using System; using SharedLibraryCore.Helpers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@ -11,7 +12,7 @@ namespace SharedLibraryCore.Interfaces
/// </summary> /// </summary>
/// <param name="networkId">network id of the players to generate the token for</param> /// <param name="networkId">network id of the players to generate the token for</param>
/// <returns>4 character string token</returns> /// <returns>4 character string token</returns>
string GenerateNextToken(long networkId); TokenState GenerateNextToken(long networkId);
/// <summary> /// <summary>
/// authorizes given token /// authorizes given token

View File

@ -21,9 +21,11 @@ namespace WebfrontCore.Controllers
try try
{ {
var client = Manager.GetPrivilegedClients()[clientId]; 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[] var claims = new[]
{ {
@ -61,12 +63,5 @@ namespace WebfrontCore.Controllers
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home"); return RedirectToAction("Index", "Home");
} }
[HttpGet]
[Authorize]
public async Task<IActionResult> GenerateLoginTokenAsync()
{
return Json(new { token = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId) });
}
} }
} }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using WebfrontCore.ViewModels; using WebfrontCore.ViewModels;
@ -182,5 +183,25 @@ namespace WebfrontCore.Controllers
command = $"!setlevel @{targetId} {level}" command = $"!setlevel @{targetId} {level}"
})); }));
} }
public async Task<IActionResult> GenerateLoginTokenForm()
{
var info = new ActionInfo()
{
ActionButtonLabel = "Generate",
Name = "GenerateLoginToken",
Action = "GenerateLoginTokenAsync",
Inputs = new List<InputInfo>()
};
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);
}
} }
} }

View File

@ -70,6 +70,7 @@
@class = "dropdown-item bg-dark text-muted text-center text-md-left", @class = "dropdown-item bg-dark text-muted text-center text-md-left",
title = "Logout of account" title = "Logout of account"
}) })
<a class="dropdown-item bg-dark text-muted text-center text-md-left profile-action" href="#" data-action="GenerateLoginToken" aria-hidden="true" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
</div> </div>
</li> </li>
<li class="nav-item text-center text-md-left"></li> <li class="nav-item text-center text-md-left"></li>

View File

@ -1,18 +0,0 @@
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH">
</script>
</environment>

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<RazorCompileOnBuild>true</RazorCompileOnBuild> <RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>true</RazorCompileOnPublish> <RazorCompileOnPublish>true</RazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion> <TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
@ -12,7 +12,7 @@
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description> <Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated servers</Description>
<Copyright>2019</Copyright> <Copyright>2019</Copyright>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl> <PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
@ -74,7 +74,5 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions> <ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
</Project> </Project>

View File

@ -6416,4 +6416,3 @@ form *, select {
position: fixed; position: fixed;
bottom: 1em; bottom: 1em;
right: 1em; } right: 1em; }

View File

@ -178,7 +178,6 @@
} }
.profile-action { .profile-action {
padding-left: 0.25em;
cursor: pointer; cursor: pointer;
} }