Merge pull request #128 from RaidMax/feature/issue-126-create-run-as-command

Feature/issue 126 create run as command
This commit is contained in:
RaidMax 2020-04-28 16:49:40 -05:00 committed by GitHub
commit f5b0167f81
32 changed files with 1683 additions and 42 deletions

View File

@ -431,7 +431,8 @@ namespace IW4MAdmin.Application
{
Name = cmd.Name,
Alias = cmd.Alias,
MinimumPermission = cmd.Permission
MinimumPermission = cmd.Permission,
AllowImpersonation = cmd.AllowImpersonation
});
}

View File

@ -144,6 +144,7 @@ namespace IW4MAdmin
catch (CommandException e)
{
Logger.WriteInfo(e.Message);
E.FailReason = GameEvent.EventFailReason.Invalid;
}
if (C != null)
@ -342,7 +343,7 @@ namespace IW4MAdmin
return false;
}
if (E.Origin.Level > EFClient.Permission.Moderator)
if (E.Origin.Level > Permission.Moderator)
{
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
}
@ -371,7 +372,7 @@ namespace IW4MAdmin
Expires = expires,
Offender = E.Target,
Offense = E.Data,
Punisher = E.Origin,
Punisher = E.ImpersonationOrigin ?? E.Origin,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
};
@ -388,7 +389,7 @@ namespace IW4MAdmin
Expires = DateTime.UtcNow,
Offender = E.Target,
Offense = E.Data,
Punisher = E.Origin,
Punisher = E.ImpersonationOrigin ?? E.Origin,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
};
@ -413,7 +414,7 @@ namespace IW4MAdmin
Expires = DateTime.UtcNow,
Offender = E.Target,
Offense = E.Message,
Punisher = E.Origin,
Punisher = E.ImpersonationOrigin ?? E.Origin,
Active = true,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
@ -432,28 +433,28 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.TempBan)
{
await TempBan(E.Data, (TimeSpan)E.Extra, E.Target, E.Origin); ;
await TempBan(E.Data, (TimeSpan)E.Extra, E.Target, E.ImpersonationOrigin ?? E.Origin); ;
}
else if (E.Type == GameEvent.EventType.Ban)
{
bool isEvade = E.Extra != null ? (bool)E.Extra : false;
await Ban(E.Data, E.Target, E.Origin, isEvade);
await Ban(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin, isEvade);
}
else if (E.Type == GameEvent.EventType.Unban)
{
await Unban(E.Data, E.Target, E.Origin);
await Unban(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin);
}
else if (E.Type == GameEvent.EventType.Kick)
{
await Kick(E.Data, E.Target, E.Origin);
await Kick(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin);
}
else if (E.Type == GameEvent.EventType.Warn)
{
await Warn(E.Data, E.Target, E.Origin);
await Warn(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin);
}
else if (E.Type == GameEvent.EventType.Disconnect)

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Configuration;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Repositories;
using System;
using System.Linq;
using System.Text;
@ -284,6 +285,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider =>
{

View File

@ -16,9 +16,23 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
{
OffensiveWords = new List<string>()
{
@"\s*n+.*i+.*g+.*e+.*r+\s*",
@"\s*n+.*i+.*g+.*a+\s*",
@"\s*f+u+.*c+.*k+.*\s*"
@"(ph|f)[a@]g[s\$]?",
@"(ph|f)[a@]gg[i1]ng",
@"(ph|f)[a@]gg?[o0][t\+][s\$]?",
@"(ph|f)[a@]gg[s\$]",
@"(ph|f)[e3][l1][l1]?[a@][t\+][i1][o0]",
@"(ph|f)u(c|k|ck|q)",
@"(ph|f)u(c|k|ck|q)[s\$]?",
@"(c|k|ck|q)un[t\+][l1][i1](c|k|ck|q)",
@"(c|k|ck|q)un[t\+][l1][i1](c|k|ck|q)[e3]r",
@"(c|k|ck|q)un[t\+][l1][i1](c|k|ck|q)[i1]ng",
@"b[i1][t\+]ch[s\$]?",
@"b[i1][t\+]ch[e3]r[s\$]?",
@"b[i1][t\+]ch[e3][s\$]",
@"b[i1][t\+]ch[i1]ng?",
@"n[i1]gg?[e3]r[s\$]?",
@"[s\$]h[i1][t\+][s\$]?",
@"[s\$][l1]u[t\+][s\$]?"
};
var loc = Utilities.CurrentLocalization.LocalizationIndex;

View File

@ -19,6 +19,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
Alias = "rs";
Permission = EFClient.Permission.User;
RequiresTarget = false;
//AllowImpersonation = true;
}
public override async Task ExecuteAsync(GameEvent E)

View File

@ -117,5 +117,10 @@ namespace SharedLibraryCore
/// Argument list for the command
/// </summary>
public CommandArgument[] Arguments { get; protected set; } = new CommandArgument[0];
/// <summary>
/// indicates if this command allows impersonation (run as)
/// </summary>
public bool AllowImpersonation { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Commands
{
public static class CommandExtensions
{
public static bool IsTargetingSelf(this GameEvent gameEvent) => gameEvent.Origin?.Equals(gameEvent.Target) ?? false;
public static bool CanPerformActionOnTarget(this GameEvent gameEvent) => gameEvent.Origin?.Level > gameEvent.Target?.Level;
}
}

View File

@ -21,7 +21,8 @@ namespace SharedLibraryCore.Commands
Command C = null;
foreach (Command cmd in Manager.GetCommands())
{
if (cmd.Name == CommandString.ToLower() || cmd.Alias == CommandString.ToLower())
if (cmd.Name.Equals(CommandString, StringComparison.OrdinalIgnoreCase) ||
(cmd.Alias ?? "").Equals(CommandString, StringComparison.OrdinalIgnoreCase))
{
C = cmd;
}
@ -33,6 +34,12 @@ namespace SharedLibraryCore.Commands
throw new CommandException($"{E.Origin} entered unknown command \"{CommandString}\"");
}
if (!C.AllowImpersonation && E.ImpersonationOrigin != null)
{
E.ImpersonationOrigin.Tell(loc["COMMANDS_RUN_AS_FAIL"]);
throw new CommandException($"Command {C.Name} cannot be run as another client");
}
E.Data = E.Data.RemoveWords(1);
String[] Args = E.Data.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// todo: the code below can be cleaned up

View File

@ -1434,6 +1434,7 @@ namespace SharedLibraryCore.Commands
Alias = "sp";
Permission = Permission.Moderator;
RequiresTarget = false;
AllowImpersonation = true;
Arguments = new[]
{
new CommandArgument()

View File

@ -0,0 +1,65 @@
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Commands
{
public class RunAsCommand : Command
{
public RunAsCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
{
Name = "runas";
Description = lookup["COMMANDS_RUN_AS_DESC"];
Alias = "ra";
Permission = EFClient.Permission.Moderator;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = lookup["COMMANDS_ARGS_COMMANDS"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent E)
{
if (E.IsTargetingSelf())
{
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SELF"]);
return;
}
if (!E.CanPerformActionOnTarget())
{
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_FAIL_PERM"]);
return;
}
string cmd = $"{Utilities.CommandPrefix}{E.Data}";
var impersonatedCommandEvent = new GameEvent()
{
Type = GameEvent.EventType.Command,
Origin = E.Target,
ImpersonationOrigin = E.Origin,
Message = cmd,
Data = cmd,
Owner = E.Owner
};
E.Owner.Manager.GetEventHandler().AddEvent(impersonatedCommandEvent);
var result = await impersonatedCommandEvent.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken);
var response = E.Owner.CommandResult.Where(c => c.ClientId == E.Target.ClientId).ToList();
// remove the added command response
for (int i = 0; i < response.Count; i++)
{
E.Origin.Tell(_translationLookup["COMMANDS_RUN_AS_SUCCESS"].FormatExt(response[i].Response));
E.Owner.CommandResult.Remove(response[i]);
}
}
}
}

View File

@ -24,5 +24,10 @@ namespace SharedLibraryCore.Configuration
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public Permission MinimumPermission { get; set; }
/// <summary>
/// Indicates if the command can be run by another user (impersonation)
/// </summary>
public bool AllowImpersonation { get; set; }
}
}

View File

@ -71,8 +71,8 @@ namespace SharedLibraryCore.Database
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// optionsBuilder.UseLoggerFactory(_loggerFactory)
// .EnableSensitiveDataLogging();
optionsBuilder.UseLoggerFactory(_loggerFactory)
.EnableSensitiveDataLogging();
if (string.IsNullOrEmpty(_ConnectionString))
{

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
namespace SharedLibraryCore.Database.Models
{
@ -22,6 +19,7 @@ namespace SharedLibraryCore.Database.Models
public int ChangeHistoryId { get; set; }
public int OriginEntityId { get; set; }
public int TargetEntityId { get; set; }
public int? ImpersonationEntityId { get; set; }
public ChangeType TypeOfChange { get; set; }
public DateTime TimeChanged { get; set; } = DateTime.UtcNow;
[MaxLength(128)]

View File

@ -0,0 +1,57 @@
using System;
namespace SharedLibraryCore.Dtos
{
/// <summary>
/// data transfer class for audit information
/// </summary>
public class AuditInfo
{
/// <summary>
/// name of the origin entity
/// </summary>
public string OriginName { get; set; }
/// <summary>
/// id of the origin entity
/// </summary>
public int OriginId { get; set; }
/// <summary>
/// name of the target entity
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// id of the target entity
/// </summary>
public int? TargetId { get; set; }
/// <summary>
/// when the audit event occured
/// </summary>
public DateTime When { get; set; }
/// <summary>
/// what audit action occured
/// </summary>
public string Action { get; set; }
/// <summary>
/// additional comment data about the audit event
/// </summary>
public string Data { get; set; }
private string oldValue;
/// <summary>
/// previous value
/// </summary>
public string OldValue { get => oldValue ?? "--"; set => oldValue = value; }
private string newValue;
/// <summary>
/// new value
/// </summary>
public string NewValue { get => newValue ?? "--"; set => newValue = value; }
}
}

View File

@ -0,0 +1,34 @@
namespace SharedLibraryCore.Dtos
{
/// <summary>
/// pagination information holder class
/// </summary>
public class PaginationInfo
{
/// <summary>
/// how many items to skip
/// </summary>
public int Offset { get; set; }
/// <summary>
/// how many itesm to take
/// </summary>
public int Count { get; set; }
/// <summary>
/// filter query
/// </summary>
public string Filter { get; set; }
/// <summary>
/// direction of ordering
/// </summary>
public SortDirection Direction { get; set; } = SortDirection.Descending;
}
public enum SortDirection
{
Ascending,
Descending
}
}

View File

@ -227,6 +227,7 @@ namespace SharedLibraryCore
public int? GameTime { get; set; }
public EFClient Origin;
public EFClient Target;
public EFClient ImpersonationOrigin { get; set; }
public Server Owner;
public bool IsRemote { get; set; } = false;
public object Extra { get; set; }
@ -276,7 +277,7 @@ namespace SharedLibraryCore
Owner?.Logger.WriteError("Waiting for event to complete timed out");
Owner?.Logger.WriteDebug($"{Id}, {Type}, {Data}, {Extra}, {FailReason.ToString()}, {Message}, {Origin}, {Target}");
#if DEBUG
throw new Exception();
//throw new Exception();
#endif
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore.Dtos;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// describes the capabilities of the audit info repository
/// </summary>
public interface IAuditInformationRepository
{
/// <summary>
/// retrieves a list of audit information for given pagination params
/// </summary>
/// <param name="paginationInfo">pagination info</param>
/// <returns></returns>
Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo);
}
}

View File

@ -44,5 +44,10 @@ namespace SharedLibraryCore.Interfaces
/// Indicates if target is required
/// </summary>
bool RequiresTarget { get; }
/// <summary>
/// Indicates if the commands can be run as another client
/// </summary>
bool AllowImpersonation { get; }
}
}

View File

@ -0,0 +1,919 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20200423225137_AddImpersonationIdToEFChangeHistory")]
partial class AddImpersonationIdToEFChangeHistory
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.3");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<int>("CurrentSessionLength")
.HasColumnType("INTEGER");
b.Property<double>("CurrentStrain")
.HasColumnType("REAL");
b.Property<int>("CurrentViewAngleId")
.HasColumnType("INTEGER");
b.Property<int>("Deaths")
.HasColumnType("INTEGER");
b.Property<double>("Distance")
.HasColumnType("REAL");
b.Property<double>("EloRating")
.HasColumnType("REAL");
b.Property<int>("HitDestinationId")
.HasColumnType("INTEGER");
b.Property<int>("HitLocation")
.HasColumnType("INTEGER");
b.Property<int>("HitOriginId")
.HasColumnType("INTEGER");
b.Property<int>("HitType")
.HasColumnType("INTEGER");
b.Property<int>("Hits")
.HasColumnType("INTEGER");
b.Property<int>("Kills")
.HasColumnType("INTEGER");
b.Property<int>("LastStrainAngleId")
.HasColumnType("INTEGER");
b.Property<double>("RecoilOffset")
.HasColumnType("REAL");
b.Property<double>("SessionAngleOffset")
.HasColumnType("REAL");
b.Property<double>("SessionAverageSnapValue")
.HasColumnType("REAL");
b.Property<double>("SessionSPM")
.HasColumnType("REAL");
b.Property<int>("SessionScore")
.HasColumnType("INTEGER");
b.Property<int>("SessionSnapHits")
.HasColumnType("INTEGER");
b.Property<double>("StrainAngleBetween")
.HasColumnType("REAL");
b.Property<int>("TimeSinceLastEvent")
.HasColumnType("INTEGER");
b.Property<int>("WeaponId")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.Property<int>("ACSnapshotVector3Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("SnapshotId")
.HasColumnType("INTEGER");
b.Property<int>("Vector3Id")
.HasColumnType("INTEGER");
b.HasKey("ACSnapshotVector3Id");
b.HasIndex("SnapshotId");
b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("AttackerId")
.HasColumnType("INTEGER");
b.Property<int>("Damage")
.HasColumnType("INTEGER");
b.Property<int?>("DeathOriginVector3Id")
.HasColumnType("INTEGER");
b.Property<int>("DeathType")
.HasColumnType("INTEGER");
b.Property<double>("Fraction")
.HasColumnType("REAL");
b.Property<int>("HitLoc")
.HasColumnType("INTEGER");
b.Property<bool>("IsKill")
.HasColumnType("INTEGER");
b.Property<int?>("KillOriginVector3Id")
.HasColumnType("INTEGER");
b.Property<int>("Map")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<int>("VictimId")
.HasColumnType("INTEGER");
b.Property<int?>("ViewAnglesVector3Id")
.HasColumnType("INTEGER");
b.Property<double>("VisibilityPercentage")
.HasColumnType("REAL");
b.Property<int>("Weapon")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("TimeSent")
.HasColumnType("TEXT");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<double>("AverageRecoilOffset")
.HasColumnType("REAL");
b.Property<double>("AverageSnapValue")
.HasColumnType("REAL");
b.Property<int>("Deaths")
.HasColumnType("INTEGER");
b.Property<double>("EloRating")
.HasColumnType("REAL");
b.Property<int>("Kills")
.HasColumnType("INTEGER");
b.Property<double>("MaxStrain")
.HasColumnType("REAL");
b.Property<double>("RollingWeightedKDR")
.HasColumnType("REAL");
b.Property<double>("SPM")
.HasColumnType("REAL");
b.Property<double>("Skill")
.HasColumnType("REAL");
b.Property<int>("SnapHitCount")
.HasColumnType("INTEGER");
b.Property<int>("TimePlayed")
.HasColumnType("INTEGER");
b.Property<double>("VisionAverage")
.HasColumnType("REAL");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId")
.HasColumnType("INTEGER");
b.Property<long>("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId")
.HasColumnType("INTEGER");
b.Property<int>("HitCount")
.HasColumnType("INTEGER");
b.Property<float>("HitOffsetAverage")
.HasColumnType("REAL");
b.Property<int>("Location")
.HasColumnType("INTEGER");
b.Property<float>("MaxAngleDistance")
.HasColumnType("REAL");
b.HasKey("HitLocationCountId");
b.HasIndex("EFClientStatisticsServerId");
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ActivityAmount")
.HasColumnType("INTEGER");
b.Property<bool>("Newest")
.HasColumnType("INTEGER");
b.Property<double>("Performance")
.HasColumnType("REAL");
b.Property<int>("Ranking")
.HasColumnType("INTEGER");
b.Property<int>("RatingHistoryId")
.HasColumnType("INTEGER");
b.Property<long?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("EndPoint")
.HasColumnType("TEXT");
b.Property<int?>("GameName")
.HasColumnType("INTEGER");
b.Property<int>("Port")
.HasColumnType("INTEGER");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<long>("TotalKills")
.HasColumnType("INTEGER");
b.Property<long>("TotalPlayTime")
.HasColumnType("INTEGER");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<DateTime>("DateAdded")
.HasColumnType("TEXT");
b.Property<int?>("IPAddress")
.HasColumnType("INTEGER");
b.Property<int>("LinkId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(24);
b.Property<string>("SearchableName")
.HasColumnType("TEXT")
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress")
.IsUnique();
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("Comment")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("CurrentValue")
.HasColumnType("TEXT");
b.Property<int?>("ImpersonationEntityId")
.HasColumnType("INTEGER");
b.Property<int>("OriginEntityId")
.HasColumnType("INTEGER");
b.Property<string>("PreviousValue")
.HasColumnType("TEXT");
b.Property<int>("TargetEntityId")
.HasColumnType("INTEGER");
b.Property<DateTime>("TimeChanged")
.HasColumnType("TEXT");
b.Property<int>("TypeOfChange")
.HasColumnType("INTEGER");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("AliasLinkId")
.HasColumnType("INTEGER");
b.Property<int>("Connections")
.HasColumnType("INTEGER");
b.Property<int>("CurrentAliasId")
.HasColumnType("INTEGER");
b.Property<DateTime>("FirstConnection")
.HasColumnType("TEXT");
b.Property<DateTime>("LastConnection")
.HasColumnType("TEXT");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<bool>("Masked")
.HasColumnType("INTEGER");
b.Property<long>("NetworkId")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<string>("PasswordSalt")
.HasColumnType("TEXT");
b.Property<int>("TotalConnectionTime")
.HasColumnType("INTEGER");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Extra")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<DateTime>("Updated")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("AutomatedOffense")
.HasColumnType("TEXT");
b.Property<DateTime?>("Expires")
.HasColumnType("TEXT");
b.Property<bool>("IsEvadedOffense")
.HasColumnType("INTEGER");
b.Property<int>("LinkId")
.HasColumnType("INTEGER");
b.Property<int>("OffenderId")
.HasColumnType("INTEGER");
b.Property<string>("Offense")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("PunisherId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<float>("X")
.HasColumnType("REAL");
b.Property<float>("Y")
.HasColumnType("REAL");
b.Property<float>("Z")
.HasColumnType("REAL");
b.HasKey("Vector3Id");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
.WithMany()
.HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("EFClientStatisticsClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", null)
.WithMany("HitLocations")
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddImpersonationIdToEFChangeHistory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ImpersonationEntityId",
table: "EFChangeHistory",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ImpersonationEntityId",
table: "EFChangeHistory");
}
}
}

View File

@ -14,7 +14,7 @@ namespace SharedLibraryCore.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.0");
.HasAnnotation("ProductVersion", "3.1.3");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
@ -511,6 +511,9 @@ namespace SharedLibraryCore.Migrations
b.Property<string>("CurrentValue")
.HasColumnType("TEXT");
b.Property<int?>("ImpersonationEntityId")
.HasColumnType("INTEGER");
b.Property<int>("OriginEntityId")
.HasColumnType("INTEGER");

View File

@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Repositories
{
/// <summary>
/// implementation if IAuditInformationRepository
/// </summary>
public class AuditInformationRepository : IAuditInformationRepository
{
private readonly IDatabaseContextFactory _contextFactory;
public AuditInformationRepository(IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
/// <inheritdoc/>
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo)
{
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
var iqItems = (from change in ctx.EFChangeHistory
where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban
orderby change.TimeChanged descending
join originClient in ctx.Clients
on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId
join targetClient in ctx.Clients
on change.TargetEntityId equals targetClient.ClientId
into targetChange
from targetClient in targetChange.DefaultIfEmpty()
select new AuditInfo()
{
Action = change.TypeOfChange.ToString(),
OriginName = originClient.CurrentAlias.Name,
OriginId = originClient.ClientId,
TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name,
TargetId = targetClient == null ? new int?() : targetClient.ClientId,
When = change.TimeChanged,
Data = change.Comment,
OldValue = change.PreviousValue,
NewValue = change.CurrentValue
})
.Skip(paginationInfo.Offset)
.Take(paginationInfo.Count);
return await iqItems.ToListAsync();
}
}
}
}

View File

@ -26,6 +26,7 @@ namespace SharedLibraryCore.Services
{
OriginEntityId = e.Origin.ClientId,
TargetEntityId = e.Target.ClientId,
ImpersonationEntityId = e.ImpersonationOrigin?.ClientId,
TypeOfChange = EFChangeHistory.ChangeType.Ban,
Comment = e.Data
};
@ -43,6 +44,7 @@ namespace SharedLibraryCore.Services
{
OriginEntityId = e.Origin.ClientId,
TargetEntityId = e.Target?.ClientId ?? 0,
ImpersonationEntityId = e.ImpersonationOrigin?.ClientId,
Comment = "Executed command",
CurrentValue = e.Message,
TypeOfChange = EFChangeHistory.ChangeType.Command
@ -53,6 +55,7 @@ namespace SharedLibraryCore.Services
{
OriginEntityId = e.Origin.ClientId,
TargetEntityId = e.Target.ClientId,
ImpersonationEntityId = e.ImpersonationOrigin?.ClientId,
Comment = "Changed permission level",
TypeOfChange = EFChangeHistory.ChangeType.Permission,
CurrentValue = ((EFClient.Permission)e.Extra).ToString()

View File

@ -32,9 +32,9 @@ namespace SharedLibraryCore
#endif
public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>());
public static TimeSpan DefaultCommandTimeout = new TimeSpan(0, 0, 25);
public static TimeSpan DefaultCommandTimeout { get; set; } = new TimeSpan(0, 0, 25);
public static char[] DirectorySeparatorChars = new[] { '\\', '/' };
public static char CommandPrefix { get; set; } = '!';
public static EFClient IW4MAdminClient(Server server = null)
{
return new EFClient()

View File

@ -0,0 +1,178 @@
using NUnit.Framework;
using System;
using SharedLibraryCore.Interfaces;
using IW4MAdmin;
using FakeItEasy;
using IW4MAdmin.Application.EventParsers;
using System.Linq;
using IW4MAdmin.Plugins.Stats.Models;
using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Plugins.Stats.Config;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using Microsoft.Extensions.DependencyInjection;
using IW4MAdmin.Plugins.Stats.Helpers;
using ApplicationTests.Fixtures;
using System.Threading.Tasks;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore;
using ApplicationTests.Mocks;
namespace ApplicationTests
{
[TestFixture]
public class CommandTests
{
ILogger logger;
private IServiceProvider serviceProvider;
private ITranslationLookup transLookup;
private CommandConfiguration cmdConfig;
private MockEventHandler mockEventHandler;
[SetUp]
public void Setup()
{
logger = A.Fake<ILogger>();
cmdConfig = new CommandConfiguration();
serviceProvider = new ServiceCollection()
.BuildBase()
.BuildServiceProvider();
mockEventHandler = new MockEventHandler(true);
A.CallTo(() => serviceProvider.GetRequiredService<IManager>().GetEventHandler())
.Returns(mockEventHandler);
var mgr = serviceProvider.GetRequiredService<IManager>();
transLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
A.CallTo(() => mgr.GetCommands())
.Returns(new Command[]
{
new ImpersonatableCommand(cmdConfig, transLookup),
new NonImpersonatableCommand(cmdConfig, transLookup)
});
//Utilities.DefaultCommandTimeout = new TimeSpan(0, 0, 2);
}
#region RUNAS
[Test]
public async Task Test_RunAsFailsOnSelf()
{
var cmd = new RunAsCommand(cmdConfig, transLookup);
var server = serviceProvider.GetRequiredService<IW4MServer>();
var target = ClientGenerators.CreateBasicClient(server);
var gameEvent = new GameEvent()
{
Target = target,
Origin = target
};
await cmd.ExecuteAsync(gameEvent);
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Tell));
Assert.IsEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Command));
}
[Test]
public async Task Test_RunAsFailsOnHigherPrivilege()
{
var cmd = new RunAsCommand(cmdConfig, transLookup);
var server = serviceProvider.GetRequiredService<IW4MServer>();
var target = ClientGenerators.CreateBasicClient(server);
target.Level = EFClient.Permission.Administrator;
var origin = ClientGenerators.CreateBasicClient(server);
origin.NetworkId = 100;
origin.Level = EFClient.Permission.Moderator;
var gameEvent = new GameEvent()
{
Target = target,
Origin = origin
};
await cmd.ExecuteAsync(gameEvent);
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Tell));
Assert.IsEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Command));
}
[Test]
public async Task Test_RunAsFailsOnSamePrivilege()
{
var cmd = new RunAsCommand(cmdConfig, transLookup);
var server = serviceProvider.GetRequiredService<IW4MServer>();
var target = ClientGenerators.CreateBasicClient(server);
target.Level = EFClient.Permission.Administrator;
var origin = ClientGenerators.CreateBasicClient(server);
origin.NetworkId = 100;
origin.Level = EFClient.Permission.Administrator;
var gameEvent = new GameEvent()
{
Target = target,
Origin = origin
};
await cmd.ExecuteAsync(gameEvent);
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Tell));
Assert.IsEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Command));
}
[Test]
public async Task Test_RunAsFailsOnDisallowedCommand()
{
var cmd = new RunAsCommand(cmdConfig, transLookup);
var server = serviceProvider.GetRequiredService<IW4MServer>();
var target = ClientGenerators.CreateBasicClient(server);
target.Level = EFClient.Permission.Moderator;
var origin = ClientGenerators.CreateBasicClient(server);
origin.NetworkId = 100;
origin.Level = EFClient.Permission.Administrator;
var gameEvent = new GameEvent()
{
Target = target,
Origin = origin,
Owner = server,
Data = nameof(NonImpersonatableCommand)
};
await cmd.ExecuteAsync(gameEvent);
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Tell));
// failed when validating the command
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Command && _event.FailReason == GameEvent.EventFailReason.Invalid));
}
[Test]
public async Task Test_RunAsQueuesEventAndResponse()
{
var cmd = new RunAsCommand(cmdConfig, transLookup);
var server = serviceProvider.GetRequiredService<IW4MServer>();
var target = ClientGenerators.CreateBasicClient(server);
target.Level = EFClient.Permission.Moderator;
var origin = ClientGenerators.CreateBasicClient(server);
origin.NetworkId = 100;
origin.Level = EFClient.Permission.Administrator;
var gameEvent = new GameEvent()
{
Target = target,
Origin = origin,
Data = nameof(ImpersonatableCommand),
Owner = server
};
await cmd.ExecuteAsync(gameEvent);
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Tell /*&& _event.Target == origin todo: fake the command result*/ ));
Assert.IsNotEmpty(mockEventHandler.Events.Where(_event => _event.Type == GameEvent.EventType.Command && !_event.Failed));
}
#endregion
}
}

View File

@ -0,0 +1,36 @@
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using System;
using System.Threading.Tasks;
namespace ApplicationTests.Mocks
{
class ImpersonatableCommand : Command
{
public ImpersonatableCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
{
AllowImpersonation = true;
Name = nameof(ImpersonatableCommand);
}
public override Task ExecuteAsync(GameEvent E)
{
E.Origin.Tell("test");
return Task.CompletedTask;
}
}
class NonImpersonatableCommand : Command
{
public NonImpersonatableCommand(CommandConfiguration config, ITranslationLookup lookup) : base(config, lookup)
{
Name = nameof(NonImpersonatableCommand);
}
public override Task ExecuteAsync(GameEvent E)
{
return Task.CompletedTask;
}
}
}

View File

@ -7,10 +7,22 @@ namespace ApplicationTests.Mocks
class MockEventHandler : IEventHandler
{
public IList<GameEvent> Events = new List<GameEvent>();
private readonly bool _autoExecute;
public MockEventHandler(bool autoExecute = false)
{
_autoExecute = autoExecute;
}
public void AddEvent(GameEvent gameEvent)
{
Events.Add(gameEvent);
if (_autoExecute)
{
gameEvent.Owner?.ExecuteEvent(gameEvent);
gameEvent.Complete();
}
}
}
}

View File

@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class AdminController : BaseController
{
private readonly IAuditInformationRepository _auditInformationRepository;
private readonly ITranslationLookup _translationLookup;
private static readonly int DEFAULT_COUNT = 25;
public AdminController(IManager manager, IAuditInformationRepository auditInformationRepository, ITranslationLookup translationLookup) : base(manager)
{
_auditInformationRepository = auditInformationRepository;
_translationLookup = translationLookup;
}
[Authorize]
public async Task<IActionResult> AuditLog()
{
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
ViewBag.IsFluid = true;
ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"];
ViewBag.InitialOffset = DEFAULT_COUNT;
var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationInfo()
{
Count = DEFAULT_COUNT
});
return View(auditItems);
}
public async Task<IActionResult> ListAuditLog([FromQuery] PaginationInfo paginationInfo)
{
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo);
return PartialView("_ListAuditLog", auditItems);
}
}
}

View File

@ -101,7 +101,12 @@ namespace WebfrontCore
#endif
services.AddSingleton(Program.Manager);
// todo: this needs to be handled more gracefully
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IDatabaseContextFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IAuditInformationRepository>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ITranslationLookup>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -0,0 +1,34 @@
@{
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
}
<h4 class="pb-3 text-center">@ViewBag.Title</h4>
<table class="table table-striped">
<thead class="d-none d-lg-table-header-group">
<tr class="bg-primary pt-2 pb-2">
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
<!--<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>-->
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
<th scope="col" class="text-right">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
</tr>
</thead>
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
<partial name="_ListAuditLog" />
</tbody>
</table>
<span id="load_audit_log_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
@section scripts {
<environment include="Development">
<script type="text/javascript" src="~/js/loader.js"></script>
</environment>
<script>
$(document).ready(function () {
initLoader('/Admin/ListAuditLog', '#audit_log_table_body', @ViewBag.IntialOffset);
});
</script>
}

View File

@ -0,0 +1,99 @@
@using SharedLibraryCore.Dtos
@model IEnumerable<AuditInfo>
@{
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
}
@foreach (var info in Model)
{
<!-- mobile -->
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
<td class="text-light">
@info.Action
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
<td>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
<color-code value="@info.OriginName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
<td>
@if (info.TargetId != null)
{
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
<color-code value="@info.TargetName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
}
else
{
<span>--</span>
}
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
<td class="text-light">
@info.Data
</td>
</tr>
@*<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>
<td class="text-light">
@info.OldValue
</td>
</tr>*@
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
<td class="text-light">
@info.NewValue
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="w-25 bg-primary" style="border-bottom: 1px solid #222">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
<td class="text-light mb-2 border-bottom">
@info.When.ToString()
</td>
</tr>
<!-- desktop -->
<tr class="d-none d-lg-table-row">
<td class="text-light font-weight-bold">
@info.Action
</td>
<td>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
<color-code value="@info.OriginName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</td>
<td>
@if (info.TargetId != null)
{
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
<color-code value="@info.TargetName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
}
else
{
<span>--</span>
}
</td>
<td class="text-light">
@info.Data
@*<td class="text-light">
@info.OldValue
</td>*@
<td class="text-light">
@info.NewValue
</td>
<td class="text-light text-right">
@info.When.ToString()
</td>
</tr>
}

View File

@ -39,38 +39,39 @@
<li class="nav-item text-center text-lg-left">@Html.ActionLink(loc["WEBFRONT_NAV_HELP"], "Help", "Home", new { area = "" }, new { @class = "nav-link" })</li>
@foreach (var _page in ViewBag.Pages)
{
<li class="nav-item text-center text-lg-left">
<a class="nav-link" href="@_page.Location">@_page.Name</a>
</li>
<li class="nav-item text-center text-lg-left">
<a class="nav-link" href="@_page.Location">@_page.Name</a>
</li>
}
<li class="nav-item text-center text-lg-left"></li>
@if (!string.IsNullOrEmpty(ViewBag.SocialLink))
{
<li class="nav-item text-center text-lg-left"><a href="@ViewBag.SocialLink" class="nav-link" target="_blank">@ViewBag.SocialTitle</a></li>
<li class="nav-item text-center text-lg-left"><a href="@ViewBag.SocialLink" class="nav-link" target="_blank">@ViewBag.SocialTitle</a></li>
}
@if (ViewBag.Authorized)
{
<li class="nav-link dropdown text-center text-lg-left p-0">
<a href="#" class="nav-link oi oi-person dropdown-toggle oi-fix-navbar w-100" id="account_dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
<li class="nav-link dropdown text-center text-lg-left p-0">
<a href="#" class="nav-link oi oi-person dropdown-toggle oi-fix-navbar w-100" id="account_dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu p-0" aria-labelledby="account_dropdown">
<a asp-controller="Console" asp-action="Index" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_CONSOLE"]</a>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@ViewBag.User.ClientId" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_PROFILE"]</a>
@if (ViewBag.User.Level >= SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
<div class="dropdown-menu p-0" aria-labelledby="account_dropdown">
<a asp-controller="Console" asp-action="Index" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_CONSOLE"]</a>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@ViewBag.User.ClientId" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_PROFILE"]</a>
@if (ViewBag.User.Level >= SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
{
<a asp-controller="Configuration" asp-action="Edit" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_EDIT_CONFIGURATION"]</a>
<a asp-controller="Configuration" asp-action="Edit" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_EDIT_CONFIGURATION"]</a>
}
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="RecentClients" title="@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]">@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="GenerateLoginToken" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
<a asp-controller="Account" asp-action="LogoutAsync" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_LOGOUT"]</a>
</div>
</li>
<a asp-controller="Admin" asp-action="AuditLog" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_AUDIT_LOG"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="RecentClients" title="@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]">@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="GenerateLoginToken" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
<a asp-controller="Account" asp-action="LogoutAsync" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_LOGOUT"]</a>
</div>
</li>
}
else
{
<li class="nav-item text-center text-md-left">
<a href="#" id="profile_action_login_btn" class="nav-link profile-action oi oi-key oi-fix-navbar w-100" title="Login" data-action="login" aria-hidden="true"></a>
</li>
<li class="nav-item text-center text-md-left">
<a href="#" id="profile_action_login_btn" class="nav-link profile-action oi oi-key oi-fix-navbar w-100" title="Login" data-action="login" aria-hidden="true"></a>
</li>
}
</ul>
<form class="form-inline text-primary pt-3 pb-3" method="get" action="/Client/FindAsync">