diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs
index db4cce295..9729dc426 100644
--- a/Application/ApplicationManager.cs
+++ b/Application/ApplicationManager.cs
@@ -431,7 +431,8 @@ namespace IW4MAdmin.Application
{
Name = cmd.Name,
Alias = cmd.Alias,
- MinimumPermission = cmd.Permission
+ MinimumPermission = cmd.Permission,
+ AllowImpersonation = cmd.AllowImpersonation
});
}
diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs
index 3039a49c0..b54bf3dd8 100644
--- a/Application/IW4MServer.cs
+++ b/Application/IW4MServer.cs
@@ -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)
diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs
index a07d2bc3c..20fd80318 100644
--- a/Plugins/Stats/Commands/ResetStats.cs
+++ b/Plugins/Stats/Commands/ResetStats.cs
@@ -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)
diff --git a/SharedLibraryCore/Command.cs b/SharedLibraryCore/Command.cs
index a9ea491f0..cbde52552 100644
--- a/SharedLibraryCore/Command.cs
+++ b/SharedLibraryCore/Command.cs
@@ -117,5 +117,10 @@ namespace SharedLibraryCore
/// Argument list for the command
///
public CommandArgument[] Arguments { get; protected set; } = new CommandArgument[0];
+
+ ///
+ /// indicates if this command allows impersonation (run as)
+ ///
+ public bool AllowImpersonation { get; set; }
}
}
diff --git a/SharedLibraryCore/Commands/CommandExtensions.cs b/SharedLibraryCore/Commands/CommandExtensions.cs
new file mode 100644
index 000000000..09892ed19
--- /dev/null
+++ b/SharedLibraryCore/Commands/CommandExtensions.cs
@@ -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;
+ }
+}
diff --git a/SharedLibraryCore/Commands/CommandProcessing.cs b/SharedLibraryCore/Commands/CommandProcessing.cs
index 5a3c13a98..5f62331d1 100644
--- a/SharedLibraryCore/Commands/CommandProcessing.cs
+++ b/SharedLibraryCore/Commands/CommandProcessing.cs
@@ -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
diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs
index e1c068795..27412f032 100644
--- a/SharedLibraryCore/Commands/NativeCommands.cs
+++ b/SharedLibraryCore/Commands/NativeCommands.cs
@@ -1434,6 +1434,7 @@ namespace SharedLibraryCore.Commands
Alias = "sp";
Permission = Permission.Moderator;
RequiresTarget = false;
+ AllowImpersonation = true;
Arguments = new[]
{
new CommandArgument()
diff --git a/SharedLibraryCore/Commands/RunAsCommand.cs b/SharedLibraryCore/Commands/RunAsCommand.cs
new file mode 100644
index 000000000..67e7ac477
--- /dev/null
+++ b/SharedLibraryCore/Commands/RunAsCommand.cs
@@ -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]);
+ }
+ }
+ }
+}
diff --git a/SharedLibraryCore/Configuration/CommandProperties.cs b/SharedLibraryCore/Configuration/CommandProperties.cs
index 8271a3683..9eab8a9b7 100644
--- a/SharedLibraryCore/Configuration/CommandProperties.cs
+++ b/SharedLibraryCore/Configuration/CommandProperties.cs
@@ -24,5 +24,10 @@ namespace SharedLibraryCore.Configuration
///
[JsonConverter(typeof(StringEnumConverter))]
public Permission MinimumPermission { get; set; }
+
+ ///
+ /// Indicates if the command can be run by another user (impersonation)
+ ///
+ public bool AllowImpersonation { get; set; }
}
}
diff --git a/SharedLibraryCore/Database/Models/EFChangeHistory.cs b/SharedLibraryCore/Database/Models/EFChangeHistory.cs
index c1b040be4..fae53ab0f 100644
--- a/SharedLibraryCore/Database/Models/EFChangeHistory.cs
+++ b/SharedLibraryCore/Database/Models/EFChangeHistory.cs
@@ -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)]
diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs
index 9d78585f9..490edd74d 100644
--- a/SharedLibraryCore/Events/GameEvent.cs
+++ b/SharedLibraryCore/Events/GameEvent.cs
@@ -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
}
diff --git a/SharedLibraryCore/Interfaces/IManagerCommand.cs b/SharedLibraryCore/Interfaces/IManagerCommand.cs
index 7ab87395c..e0d35fbea 100644
--- a/SharedLibraryCore/Interfaces/IManagerCommand.cs
+++ b/SharedLibraryCore/Interfaces/IManagerCommand.cs
@@ -44,5 +44,10 @@ namespace SharedLibraryCore.Interfaces
/// Indicates if target is required
///
bool RequiresTarget { get; }
+
+ ///
+ /// Indicates if the commands can be run as another client
+ ///
+ bool AllowImpersonation { get; }
}
}
diff --git a/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.Designer.cs b/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.Designer.cs
new file mode 100644
index 000000000..9720f13f5
--- /dev/null
+++ b/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.Designer.cs
@@ -0,0 +1,919 @@
+//
+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("SnapshotId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClientId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CurrentSessionLength")
+ .HasColumnType("INTEGER");
+
+ b.Property("CurrentStrain")
+ .HasColumnType("REAL");
+
+ b.Property("CurrentViewAngleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Deaths")
+ .HasColumnType("INTEGER");
+
+ b.Property("Distance")
+ .HasColumnType("REAL");
+
+ b.Property("EloRating")
+ .HasColumnType("REAL");
+
+ b.Property("HitDestinationId")
+ .HasColumnType("INTEGER");
+
+ b.Property("HitLocation")
+ .HasColumnType("INTEGER");
+
+ b.Property("HitOriginId")
+ .HasColumnType("INTEGER");
+
+ b.Property("HitType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Hits")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kills")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastStrainAngleId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RecoilOffset")
+ .HasColumnType("REAL");
+
+ b.Property("SessionAngleOffset")
+ .HasColumnType("REAL");
+
+ b.Property("SessionAverageSnapValue")
+ .HasColumnType("REAL");
+
+ b.Property("SessionSPM")
+ .HasColumnType("REAL");
+
+ b.Property("SessionScore")
+ .HasColumnType("INTEGER");
+
+ b.Property("SessionSnapHits")
+ .HasColumnType("INTEGER");
+
+ b.Property("StrainAngleBetween")
+ .HasColumnType("REAL");
+
+ b.Property("TimeSinceLastEvent")
+ .HasColumnType("INTEGER");
+
+ b.Property("WeaponId")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("ACSnapshotVector3Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("SnapshotId")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("KillId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("AttackerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Damage")
+ .HasColumnType("INTEGER");
+
+ b.Property("DeathOriginVector3Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("DeathType")
+ .HasColumnType("INTEGER");
+
+ b.Property("Fraction")
+ .HasColumnType("REAL");
+
+ b.Property("HitLoc")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsKill")
+ .HasColumnType("INTEGER");
+
+ b.Property("KillOriginVector3Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("Map")
+ .HasColumnType("INTEGER");
+
+ b.Property("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VictimId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ViewAnglesVector3Id")
+ .HasColumnType("INTEGER");
+
+ b.Property("VisibilityPercentage")
+ .HasColumnType("REAL");
+
+ b.Property("Weapon")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("MessageId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClientId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Message")
+ .HasColumnType("TEXT");
+
+ b.Property("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("RatingHistoryId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClientId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("RatingHistoryId");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("EFClientRatingHistory");
+ });
+
+ modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
+ {
+ b.Property("ClientId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("AverageRecoilOffset")
+ .HasColumnType("REAL");
+
+ b.Property("AverageSnapValue")
+ .HasColumnType("REAL");
+
+ b.Property("Deaths")
+ .HasColumnType("INTEGER");
+
+ b.Property("EloRating")
+ .HasColumnType("REAL");
+
+ b.Property("Kills")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxStrain")
+ .HasColumnType("REAL");
+
+ b.Property("RollingWeightedKDR")
+ .HasColumnType("REAL");
+
+ b.Property("SPM")
+ .HasColumnType("REAL");
+
+ b.Property("Skill")
+ .HasColumnType("REAL");
+
+ b.Property("SnapHitCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimePlayed")
+ .HasColumnType("INTEGER");
+
+ b.Property("VisionAverage")
+ .HasColumnType("REAL");
+
+ b.HasKey("ClientId", "ServerId");
+
+ b.HasIndex("ServerId");
+
+ b.ToTable("EFClientStatistics");
+ });
+
+ modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
+ {
+ b.Property("HitLocationCountId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("EFClientStatisticsClientId")
+ .HasColumnName("EFClientStatisticsClientId")
+ .HasColumnType("INTEGER");
+
+ b.Property("EFClientStatisticsServerId")
+ .HasColumnName("EFClientStatisticsServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("HitCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("HitOffsetAverage")
+ .HasColumnType("REAL");
+
+ b.Property("Location")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("RatingId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ActivityAmount")
+ .HasColumnType("INTEGER");
+
+ b.Property("Newest")
+ .HasColumnType("INTEGER");
+
+ b.Property("Performance")
+ .HasColumnType("REAL");
+
+ b.Property("Ranking")
+ .HasColumnType("INTEGER");
+
+ b.Property("RatingHistoryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("EndPoint")
+ .HasColumnType("TEXT");
+
+ b.Property("GameName")
+ .HasColumnType("INTEGER");
+
+ b.Property("Port")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ServerId");
+
+ b.ToTable("EFServers");
+ });
+
+ modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
+ {
+ b.Property("StatisticId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ServerId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TotalKills")
+ .HasColumnType("INTEGER");
+
+ b.Property("TotalPlayTime")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("StatisticId");
+
+ b.HasIndex("ServerId");
+
+ b.ToTable("EFServerStatistics");
+ });
+
+ modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
+ {
+ b.Property("AliasId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("DateAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("IPAddress")
+ .HasColumnType("INTEGER");
+
+ b.Property("LinkId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(24);
+
+ b.Property("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("AliasLinkId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AliasLinkId");
+
+ b.ToTable("EFAliasLinks");
+ });
+
+ modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
+ {
+ b.Property("ChangeHistoryId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("Comment")
+ .HasColumnType("TEXT")
+ .HasMaxLength(128);
+
+ b.Property("CurrentValue")
+ .HasColumnType("TEXT");
+
+ b.Property("ImpersonationEntityId")
+ .HasColumnType("INTEGER");
+
+ b.Property("OriginEntityId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PreviousValue")
+ .HasColumnType("TEXT");
+
+ b.Property("TargetEntityId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimeChanged")
+ .HasColumnType("TEXT");
+
+ b.Property("TypeOfChange")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ChangeHistoryId");
+
+ b.ToTable("EFChangeHistory");
+ });
+
+ modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
+ {
+ b.Property("ClientId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("AliasLinkId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Connections")
+ .HasColumnType("INTEGER");
+
+ b.Property("CurrentAliasId")
+ .HasColumnType("INTEGER");
+
+ b.Property("FirstConnection")
+ .HasColumnType("TEXT");
+
+ b.Property("LastConnection")
+ .HasColumnType("TEXT");
+
+ b.Property("Level")
+ .HasColumnType("INTEGER");
+
+ b.Property("Masked")
+ .HasColumnType("INTEGER");
+
+ b.Property("NetworkId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Password")
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordSalt")
+ .HasColumnType("TEXT");
+
+ b.Property("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("MetaId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("ClientId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Extra")
+ .HasColumnType("TEXT");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(32);
+
+ b.Property("Updated")
+ .HasColumnType("TEXT");
+
+ b.Property("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("PenaltyId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Active")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutomatedOffense")
+ .HasColumnType("TEXT");
+
+ b.Property("Expires")
+ .HasColumnType("TEXT");
+
+ b.Property("IsEvadedOffense")
+ .HasColumnType("INTEGER");
+
+ b.Property("LinkId")
+ .HasColumnType("INTEGER");
+
+ b.Property("OffenderId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Offense")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("PunisherId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("Vector3Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("X")
+ .HasColumnType("REAL");
+
+ b.Property("Y")
+ .HasColumnType("REAL");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.cs b/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.cs
new file mode 100644
index 000000000..9b11124d1
--- /dev/null
+++ b/SharedLibraryCore/Migrations/20200423225137_AddImpersonationIdToEFChangeHistory.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace SharedLibraryCore.Migrations
+{
+ public partial class AddImpersonationIdToEFChangeHistory : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "ImpersonationEntityId",
+ table: "EFChangeHistory",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "ImpersonationEntityId",
+ table: "EFChangeHistory");
+ }
+ }
+}
diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs
index c36dee7f6..2620ced31 100644
--- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs
+++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs
@@ -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("CurrentValue")
.HasColumnType("TEXT");
+ b.Property("ImpersonationEntityId")
+ .HasColumnType("INTEGER");
+
b.Property("OriginEntityId")
.HasColumnType("INTEGER");
diff --git a/SharedLibraryCore/Services/ChangeHistoryService.cs b/SharedLibraryCore/Services/ChangeHistoryService.cs
index b540c008a..7b3ceefb3 100644
--- a/SharedLibraryCore/Services/ChangeHistoryService.cs
+++ b/SharedLibraryCore/Services/ChangeHistoryService.cs
@@ -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()
diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs
index ad6a342b4..80afde181 100644
--- a/SharedLibraryCore/Utilities.cs
+++ b/SharedLibraryCore/Utilities.cs
@@ -32,9 +32,9 @@ namespace SharedLibraryCore
#endif
public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary());
- 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()
diff --git a/Tests/ApplicationTests/CommandTests.cs b/Tests/ApplicationTests/CommandTests.cs
new file mode 100644
index 000000000..f6bb9ec5b
--- /dev/null
+++ b/Tests/ApplicationTests/CommandTests.cs
@@ -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();
+ cmdConfig = new CommandConfiguration();
+
+ serviceProvider = new ServiceCollection()
+ .BuildBase()
+ .BuildServiceProvider();
+
+ mockEventHandler = new MockEventHandler(true);
+ A.CallTo(() => serviceProvider.GetRequiredService().GetEventHandler())
+ .Returns(mockEventHandler);
+
+ var mgr = serviceProvider.GetRequiredService();
+ transLookup = serviceProvider.GetRequiredService();
+
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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
+ }
+}
diff --git a/Tests/ApplicationTests/Mocks/Commands.cs b/Tests/ApplicationTests/Mocks/Commands.cs
new file mode 100644
index 000000000..be6d0e2ba
--- /dev/null
+++ b/Tests/ApplicationTests/Mocks/Commands.cs
@@ -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;
+ }
+ }
+}
diff --git a/Tests/ApplicationTests/Mocks/EventHandler.cs b/Tests/ApplicationTests/Mocks/EventHandler.cs
index fda78c697..ec1caea20 100644
--- a/Tests/ApplicationTests/Mocks/EventHandler.cs
+++ b/Tests/ApplicationTests/Mocks/EventHandler.cs
@@ -7,10 +7,22 @@ namespace ApplicationTests.Mocks
class MockEventHandler : IEventHandler
{
public IList Events = new List();
+ 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();
+ }
}
}
}