implement basic run-as functionality
This commit is contained in:
RaidMax 2020-04-26 21:12:49 -05:00
parent 3645cf53ff
commit 58bfd189d0
20 changed files with 1294 additions and 18 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

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

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

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

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

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