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