Validate auth ticket event exposed to other parts of Torch and plugins
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.API.Event
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager class responsible for registration of event handlers.
|
||||
/// </summary>
|
||||
public interface IEventManager
|
||||
public interface IEventManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers all event handler methods contained in the given instance
|
||||
|
@@ -25,8 +25,7 @@ namespace Torch.Server.Managers
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedGetter(Name = "m_members")]
|
||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_members")] private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||
#pragma warning restore 649
|
||||
@@ -37,7 +36,9 @@ namespace Torch.Server.Managers
|
||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { }
|
||||
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
@@ -54,7 +55,8 @@ namespace Torch.Server.Managers
|
||||
}
|
||||
|
||||
/// <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/>
|
||||
public override void Attach()
|
||||
@@ -62,8 +64,10 @@ namespace Torch.Server.Managers
|
||||
base.Attach();
|
||||
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
|
||||
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
|
||||
_gameServerValidateAuthTicketReplacer.Replace(new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
|
||||
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse), MyGameService.GameServer);
|
||||
_gameServerValidateAuthTicketReplacer.Replace(
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -80,15 +84,20 @@ namespace Torch.Server.Managers
|
||||
|
||||
|
||||
#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;
|
||||
[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 ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
|
||||
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
|
||||
#pragma warning restore 649
|
||||
|
||||
#region CustomAuth
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
|
||||
private static Func<ulong, string> _convertSteamIDFrom64;
|
||||
@@ -96,78 +105,130 @@ namespace Torch.Server.Managers
|
||||
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
|
||||
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
|
||||
|
||||
[ReflectedMethod(Name = "UserAccepted")]
|
||||
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
|
||||
[ReflectedMethod(Name = "UserAccepted")] private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
|
||||
|
||||
[ReflectedMethod(Name = "UserRejected")]
|
||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
|
||||
[ReflectedMethod(Name = "IsClientBanned")]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
|
||||
[ReflectedMethod(Name = "IsClientKicked")]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
|
||||
|
||||
[ReflectedMethod(Name = "IsClientBanned")] private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
|
||||
[ReflectedMethod(Name = "IsClientKicked")] private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
|
||||
|
||||
[ReflectedMethod(Name = "RaiseClientKicked")]
|
||||
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
|
||||
#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
|
||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong 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);
|
||||
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
if (_waitingForGroupLocal.Count >= _waitListSize)
|
||||
_waitingForGroupLocal.RemoveAt(0);
|
||||
_waitingForGroupLocal.Add(new WaitingForGroup(steamID, response, steamOwner));
|
||||
}
|
||||
else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
else
|
||||
UserRejected(steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID)))
|
||||
JoinResult verdict;
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
this.UserAccepted(steamID);
|
||||
return;
|
||||
_log.Error(task.Exception, $"Future validation verdict faulted");
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||
return;
|
||||
}
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||
if (verdict == JoinResult.OK)
|
||||
UserAccepted(steamId);
|
||||
else
|
||||
UserRejected(steamId, verdict);
|
||||
}
|
||||
|
||||
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)
|
||||
UserAccepted(userId);
|
||||
else
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||
var wait = _waitingForGroupLocal[j];
|
||||
if (wait.SteamId == userId)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||
_userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
|
||||
base.RaiseClientJoined(steamId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -198,6 +198,7 @@
|
||||
<Compile Include="Managers\EntityControlManager.cs" />
|
||||
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
|
||||
<Compile Include="Managers\InstanceManager.cs" />
|
||||
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Initializer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
@@ -38,7 +38,9 @@ namespace Torch.Collections
|
||||
|
||||
~MtObservableCollection()
|
||||
{
|
||||
_flushEventQueue.Dispose();
|
||||
Timer queue = _flushEventQueue;
|
||||
_flushEventQueue = null;
|
||||
queue?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -208,10 +210,10 @@ namespace Torch.Collections
|
||||
return;
|
||||
_collectionEventQueue.Enqueue(e);
|
||||
// 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 =
|
||||
new Queue<NotifyCollectionChangedEventArgs>();
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
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");
|
||||
var listsFound = 0;
|
||||
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<>))
|
||||
{
|
||||
Type eventType = field.FieldType.GenericTypeArguments[0];
|
||||
@@ -70,7 +71,7 @@ namespace Torch.Event
|
||||
ParameterInfo[] ps = x.GetParameters();
|
||||
if (ps.Length != 1)
|
||||
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;
|
||||
}
|
||||
@@ -78,9 +79,12 @@ namespace Torch.Event
|
||||
/// <inheritdoc/>
|
||||
private static void RegisterHandlerInternal(IEventHandler instance)
|
||||
{
|
||||
var foundHandler = false;
|
||||
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)
|
||||
{
|
||||
var foundList = false;
|
||||
@@ -100,6 +104,9 @@ namespace Torch.Event
|
||||
}
|
||||
_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>
|
||||
|
@@ -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}"/>.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class EventShimAttribute : Attribute
|
||||
public class EventShimAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user