Validate auth ticket event exposed to other parts of Torch and plugins

This commit is contained in:
Westin Miller
2017-11-11 01:35:00 -08:00
parent fe5dfa0ea7
commit 1b0dcc9808
7 changed files with 232 additions and 65 deletions

View File

@@ -1,11 +1,12 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Torch.API.Managers;
namespace Torch.API.Event namespace Torch.API.Event
{ {
/// <summary> /// <summary>
/// Manager class responsible for registration of event handlers. /// Manager class responsible for registration of event handlers.
/// </summary> /// </summary>
public interface IEventManager public interface IEventManager : IManager
{ {
/// <summary> /// <summary>
/// Registers all event handler methods contained in the given instance /// Registers all event handler methods contained in the given instance

View File

@@ -25,8 +25,7 @@ namespace Torch.Server.Managers
private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly Logger _log = LogManager.GetCurrentClassLogger();
#pragma warning disable 649 #pragma warning disable 649
[ReflectedGetter(Name = "m_members")] [ReflectedGetter(Name = "m_members")] private static Func<MyDedicatedServerBase, List<ulong>> _members;
private static Func<MyDedicatedServerBase, List<ulong>> _members;
[ReflectedGetter(Name = "m_waitingForGroup")] [ReflectedGetter(Name = "m_waitingForGroup")]
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup; private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
#pragma warning restore 649 #pragma warning restore 649
@@ -37,7 +36,9 @@ namespace Torch.Server.Managers
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>(); private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
/// <inheritdoc /> /// <inheritdoc />
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { } public MultiplayerManagerDedicated(ITorchBase torch) : base(torch)
{
}
/// <inheritdoc /> /// <inheritdoc />
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId)); public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
@@ -54,7 +55,8 @@ namespace Torch.Server.Managers
} }
/// <inheritdoc /> /// <inheritdoc />
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) || MySandboxGame.ConfigDedicated.Banned.Contains(steamId); public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
/// <inheritdoc/> /// <inheritdoc/>
public override void Attach() public override void Attach()
@@ -62,8 +64,10 @@ namespace Torch.Server.Managers
base.Attach(); base.Attach();
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke(); _gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke(); _gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
_gameServerValidateAuthTicketReplacer.Replace(new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer); _gameServerValidateAuthTicketReplacer.Replace(
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse), MyGameService.GameServer); new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse),
MyGameService.GameServer);
_log.Info("Inserted steam authentication intercept"); _log.Info("Inserted steam authentication intercept");
} }
@@ -80,15 +84,20 @@ namespace Torch.Server.Managers
#pragma warning disable 649 #pragma warning disable 649
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")] [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse),
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory; private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse),
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory; private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer; private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer; private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
#pragma warning restore 649 #pragma warning restore 649
#region CustomAuth #region CustomAuth
#pragma warning disable 649 #pragma warning disable 649
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")] [ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
private static Func<ulong, string> _convertSteamIDFrom64; private static Func<ulong, string> _convertSteamIDFrom64;
@@ -96,78 +105,130 @@ namespace Torch.Server.Managers
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")] [ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType; private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
[ReflectedMethod(Name = "UserAccepted")] [ReflectedMethod(Name = "UserAccepted")] private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
[ReflectedMethod(Name = "UserRejected")] [ReflectedMethod(Name = "UserRejected")]
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected; private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
[ReflectedMethod(Name = "IsClientBanned")]
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned; [ReflectedMethod(Name = "IsClientBanned")] private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
[ReflectedMethod(Name = "IsClientKicked")] [ReflectedMethod(Name = "IsClientKicked")] private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
[ReflectedMethod(Name = "RaiseClientKicked")] [ReflectedMethod(Name = "RaiseClientKicked")]
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked; private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
#pragma warning restore 649 #pragma warning restore 649
private const int _waitListSize = 32;
private readonly List<WaitingForGroup> _waitingForGroupLocal = new List<WaitingForGroup>(_waitListSize);
private struct WaitingForGroup
{
public readonly ulong SteamId;
public readonly JoinResult Response;
public readonly ulong SteamOwner;
public WaitingForGroup(ulong id, JoinResult response, ulong owner)
{
SteamId = id;
Response = response;
SteamOwner = owner;
}
}
//Largely copied from SE //Largely copied from SE
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner) private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
{ {
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}"); _log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
if (IsBanned(steamOwner)) if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
RunEvent(new ValidateAuthTicketEvent(steamID, steamOwner, response, 0, true, false));
else if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
UserRejected(steamID, JoinResult.GroupIdInvalid);
else if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
lock (_waitingForGroupLocal)
{ {
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins); if (_waitingForGroupLocal.Count >= _waitListSize)
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); _waitingForGroupLocal.RemoveAt(0);
_waitingForGroupLocal.Add(new WaitingForGroup(steamID, response, steamOwner));
} }
else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner)) else
{ UserRejected(steamID, JoinResult.SteamServersOffline);
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
} }
if (response != JoinResult.OK)
private void RunEvent(ValidateAuthTicketEvent info)
{ {
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response); MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
if (info.FutureVerdict == null)
{
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
CommitVerdict(info.SteamID, JoinResult.BannedByAdmins);
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
CommitVerdict(info.SteamID, JoinResult.KickedRecently);
else if (info.SteamResponse != JoinResult.OK)
CommitVerdict(info.SteamID, info.SteamResponse);
else if (MyMultiplayer.Static.MemberLimit > 0 &&
MyMultiplayer.Static.MemberCount + 1 > MyMultiplayer.Static.MemberLimit)
CommitVerdict(info.SteamID, JoinResult.ServerFull);
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
CommitVerdict(info.SteamID, JoinResult.OK);
else if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
CommitVerdict(info.SteamID, JoinResult.OK);
else
CommitVerdict(info.SteamID, JoinResult.NotInGroup);
return; return;
} }
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
info.FutureVerdict.ContinueWith((task) =>
{ {
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull); JoinResult verdict;
return; if (task.IsFaulted)
}
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID)))
{ {
this.UserAccepted(steamID); _log.Error(task.Exception, $"Future validation verdict faulted");
return; verdict = JoinResult.TicketCanceled;
} }
if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) else
verdict = task.Result;
Torch.Invoke(() => { CommitVerdict(info.SteamID, verdict); });
});
}
private void CommitVerdict(ulong steamId, JoinResult verdict)
{ {
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid); if (verdict == JoinResult.OK)
return; UserAccepted(steamId);
} else
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) UserRejected(steamId, verdict);
{
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
return;
}
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
} }
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer) private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
{ {
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId)) lock (_waitingForGroupLocal)
for (var j = 0; j < _waitingForGroupLocal.Count; j++)
{ {
if (member || officer) var wait = _waitingForGroupLocal[j];
UserAccepted(userId); if (wait.SteamId == userId)
else {
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup); RunEvent(new ValidateAuthTicketEvent(wait.SteamId, wait.SteamOwner, wait.Response, groupId,
member, officer));
_waitingForGroupLocal.RemoveAt(j);
break;
} }
} }
}
private void UserRejected(ulong steamId, JoinResult reason)
{
_userRejected.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId, reason);
}
private void UserAccepted(ulong steamId) private void UserAccepted(ulong steamId)
{ {
_userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId); _userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
base.RaiseClientJoined(steamId); base.RaiseClientJoined(steamId);
} }
#endregion #endregion
} }
} }

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox;
using Torch.API.Event;
using Torch.Event;
using VRage.Network;
namespace Torch.Server.Managers
{
[EventShim]
internal static class MultiplayerManagerDedicatedEventShim
{
private static readonly EventList<ValidateAuthTicketEvent> _eventValidateAuthTicket =
new EventList<ValidateAuthTicketEvent>();
internal static void RaiseValidateAuthTicket(ref ValidateAuthTicketEvent info)
{
_eventValidateAuthTicket?.RaiseEvent(ref info);
}
}
// class AdminsCanJoinExample : IEventHandler
// {
// [EventHandler(SkipCancelled = false)]
// public void Handle(ref ValidateAuthTicketEvent info)
// {
// if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString())
// || (info.ServerGroup == MySandboxGame.ConfigDedicated.GroupID && info.Officer))
// info.Verdict = JoinResult.OK;
// }
// }
/// <summary>
/// Event that occurs when a player tries to connect to the server.
/// Use these values to choose a <see cref="ValidateAuthTicketEvent.Verdict"/>,
/// or leave it unset to allow the default logic to handle the request.
/// </summary>
public struct ValidateAuthTicketEvent : IEvent
{
/// <summary>
/// SteamID of the player
/// </summary>
public readonly ulong SteamID;
/// <summary>
/// SteamID of the game owner
/// </summary>
public readonly ulong SteamOwner;
/// <summary>
/// The response from steam
/// </summary>
public readonly JoinResult SteamResponse;
/// <summary>
/// ID of the queried group, or <c>0</c> if no group.
/// </summary>
public readonly ulong Group;
/// <summary>
/// Is this person a member of <see cref="Group"/>. If no group this is true.
/// </summary>
public readonly bool Member;
/// <summary>
/// Is this person an officer of <see cref="Group"/>. If no group this is false.
/// </summary>
public readonly bool Officer;
/// <summary>
/// A future verdict on this authorization request. If null, let the default logic choose.
/// </summary>
public Task<JoinResult> FutureVerdict;
internal ValidateAuthTicketEvent(ulong steamId, ulong steamOwner, JoinResult steamResponse,
ulong serverGroup, bool member, bool officer)
{
SteamID = steamId;
SteamOwner = steamOwner;
SteamResponse = steamResponse;
Group = serverGroup;
Member = member;
Officer = officer;
FutureVerdict = null;
}
/// <inheritdoc/>
public bool Cancelled => FutureVerdict != null;
}
}

View File

@@ -198,6 +198,7 @@
<Compile Include="Managers\EntityControlManager.cs" /> <Compile Include="Managers\EntityControlManager.cs" />
<Compile Include="Managers\MultiplayerManagerDedicated.cs" /> <Compile Include="Managers\MultiplayerManagerDedicated.cs" />
<Compile Include="Managers\InstanceManager.cs" /> <Compile Include="Managers\InstanceManager.cs" />
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
<Compile Include="NativeMethods.cs" /> <Compile Include="NativeMethods.cs" />
<Compile Include="Initializer.cs" /> <Compile Include="Initializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View File

@@ -38,7 +38,9 @@ namespace Torch.Collections
~MtObservableCollection() ~MtObservableCollection()
{ {
_flushEventQueue.Dispose(); Timer queue = _flushEventQueue;
_flushEventQueue = null;
queue?.Dispose();
} }
/// <summary> /// <summary>
@@ -208,10 +210,10 @@ namespace Torch.Collections
return; return;
_collectionEventQueue.Enqueue(e); _collectionEventQueue.Enqueue(e);
// In half a second, flush the events // In half a second, flush the events
_flushEventQueue.Change(500, -1); _flushEventQueue?.Change(500, -1);
} }
private readonly Timer _flushEventQueue; private Timer _flushEventQueue;
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue = private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
new Queue<NotifyCollectionChangedEventArgs>(); new Queue<NotifyCollectionChangedEventArgs>();

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -36,7 +37,7 @@ namespace Torch.Event
_log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it isn't declared singleton"); _log.Warn($"Registering type {type.FullName} as an event dispatch type, even though it isn't declared singleton");
var listsFound = 0; var listsFound = 0;
RuntimeHelpers.RunClassConstructor(type.TypeHandle); RuntimeHelpers.RunClassConstructor(type.TypeHandle);
foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(EventList<>)) if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(EventList<>))
{ {
Type eventType = field.FieldType.GenericTypeArguments[0]; Type eventType = field.FieldType.GenericTypeArguments[0];
@@ -70,7 +71,7 @@ namespace Torch.Event
ParameterInfo[] ps = x.GetParameters(); ParameterInfo[] ps = x.GetParameters();
if (ps.Length != 1) if (ps.Length != 1)
return false; return false;
return ps[0].ParameterType.IsByRef && typeof(IEvent).IsAssignableFrom(ps[0].ParameterType); return ps[0].ParameterType.IsByRef && typeof(IEvent).IsAssignableFrom(ps[0].ParameterType.GetElementType());
}); });
return exploreType.BaseType != null ? enumerable.Concat(EventHandlers(exploreType.BaseType)) : enumerable; return exploreType.BaseType != null ? enumerable.Concat(EventHandlers(exploreType.BaseType)) : enumerable;
} }
@@ -78,9 +79,12 @@ namespace Torch.Event
/// <inheritdoc/> /// <inheritdoc/>
private static void RegisterHandlerInternal(IEventHandler instance) private static void RegisterHandlerInternal(IEventHandler instance)
{ {
var foundHandler = false;
foreach (MethodInfo handler in EventHandlers(instance.GetType())) foreach (MethodInfo handler in EventHandlers(instance.GetType()))
{ {
Type eventType = handler.GetParameters()[0].ParameterType; Type eventType = handler.GetParameters()[0].ParameterType.GetElementType();
Debug.Assert(eventType != null);
foundHandler = true;
if (eventType.IsInterface) if (eventType.IsInterface)
{ {
var foundList = false; var foundList = false;
@@ -100,6 +104,9 @@ namespace Torch.Event
} }
_log.Error($"Unable to find event handler list for event type {eventType.FullName}"); _log.Error($"Unable to find event handler list for event type {eventType.FullName}");
} }
if (!foundHandler)
_log.Warn($"Found no handlers in {instance.GetType().FullName} or base types");
} }
/// <summary> /// <summary>

View File

@@ -14,7 +14,7 @@ namespace Torch.Event
/// Event shims should be singleton, and have one (or more) fields that are of type <see cref="EventList{T}"/>. /// Event shims should be singleton, and have one (or more) fields that are of type <see cref="EventList{T}"/>.
/// </remarks> /// </remarks>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
internal class EventShimAttribute : Attribute public class EventShimAttribute : Attribute
{ {
} }
} }