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>
<Company>Forever None</Company>
<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>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>

View File

@ -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)
{

View File

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

View File

@ -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<long, TokenState> _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<long, TokenState>();
@ -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()

View File

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

View File

@ -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;
//}
}
/// <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.Text;
@ -11,7 +12,7 @@ namespace SharedLibraryCore.Interfaces
/// </summary>
/// <param name="networkId">network id of the players to generate the token for</param>
/// <returns>4 character string token</returns>
string GenerateNextToken(long networkId);
TokenState GenerateNextToken(long networkId);
/// <summary>
/// authorizes given token

View File

@ -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<IActionResult> GenerateLoginTokenAsync()
{
return Json(new { token = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId) });
}
}
}

View File

@ -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<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",
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>
</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>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<RazorCompileOnBuild>true</RazorCompileOnBuild>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>true</RazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext>
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
@ -12,7 +12,7 @@
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<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>
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
@ -74,7 +74,5 @@
</ProjectReference>
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
</Project>

View File

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

View File

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