]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add new "grant_connect_bypass" admin command (#26771)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Tue, 9 Apr 2024 15:25:21 +0000 (17:25 +0200)
committerGitHub <noreply@github.com>
Tue, 9 Apr 2024 15:25:21 +0000 (17:25 +0200)
This command allows you to grant a player temporary privilege to join regardless of player cap, whitelist, etc. It does not bypass bans.

The API for this is IConnectionManager.AddTemporaryConnectBypass().

I shuffled around the logic inside ConnectionManager. Bans are now checked before panic bunker.

Content.Server/Connection/ConnectionManager.cs
Content.Server/Connection/GrantConnectBypassCommand.cs [new file with mode: 0644]
Resources/Locale/en-US/administration/commands/connection-commands.ftl [new file with mode: 0644]

index 1367cae82c1df9b011b71d422459a3ff062c2883..cd89f48d49f7f93ba83e1d2234dbe635a518c97b 100644 (file)
@@ -1,4 +1,5 @@
 using System.Collections.Immutable;
+using System.Runtime.InteropServices;
 using System.Text.Json.Nodes;
 using System.Threading.Tasks;
 using Content.Server.Database;
@@ -10,6 +11,7 @@ using Content.Shared.Players.PlayTimeTracking;
 using Robust.Server.Player;
 using Robust.Shared.Configuration;
 using Robust.Shared.Network;
+using Robust.Shared.Timing;
 
 
 namespace Content.Server.Connection
@@ -17,6 +19,18 @@ namespace Content.Server.Connection
     public interface IConnectionManager
     {
         void Initialize();
+
+        /// <summary>
+        /// Temporarily allow a user to bypass regular connection requirements.
+        /// </summary>
+        /// <remarks>
+        /// The specified user will be allowed to bypass regular player cap,
+        /// whitelist and panic bunker restrictions for <paramref name="duration"/>.
+        /// Bans are not bypassed.
+        /// </remarks>
+        /// <param name="user">The user to give a temporary bypass.</param>
+        /// <param name="duration">How long the bypass should last for.</param>
+        void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration);
     }
 
     /// <summary>
@@ -31,15 +45,31 @@ namespace Content.Server.Connection
         [Dependency] private readonly IConfigurationManager _cfg = default!;
         [Dependency] private readonly ILocalizationManager _loc = default!;
         [Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
+        [Dependency] private readonly IGameTiming _gameTiming = default!;
+        [Dependency] private readonly ILogManager _logManager = default!;
+
+        private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
+        private ISawmill _sawmill = default!;
 
         public void Initialize()
         {
+            _sawmill = _logManager.GetSawmill("connections");
+
             _netMgr.Connecting += NetMgrOnConnecting;
             _netMgr.AssignUserIdCallback = AssignUserIdCallback;
             // Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
             // _netMgr.HandleApprovalCallback = HandleApproval;
         }
 
+        public void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration)
+        {
+            ref var time = ref CollectionsMarshal.GetValueRefOrAddDefault(_temporaryBypasses, user, out _);
+            var newTime = _gameTiming.RealTime + duration;
+            // Make sure we only update the time if we wouldn't shrink it.
+            if (newTime > time)
+                time = newTime;
+        }
+
         /*
         private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
         {
@@ -109,6 +139,20 @@ namespace Content.Server.Connection
                 hwId = null;
             }
 
+            var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
+            if (bans.Count > 0)
+            {
+                var firstBan = bans[0];
+                var message = firstBan.FormatBanMessage(_cfg, _loc);
+                return (ConnectionDenyReason.Ban, message, bans);
+            }
+
+            if (HasTemporaryBypass(userId))
+            {
+                _sawmill.Verbose("User {UserId} has temporary bypass, skipping further connection checks", userId);
+                return null;
+            }
+
             var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
 
             if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
@@ -167,14 +211,6 @@ namespace Content.Server.Connection
                 return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
             }
 
-            var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
-            if (bans.Count > 0)
-            {
-                var firstBan = bans[0];
-                var message = firstBan.FormatBanMessage(_cfg, _loc);
-                return (ConnectionDenyReason.Ban, message, bans);
-            }
-
             if (_cfg.GetCVar(CCVars.WhitelistEnabled))
             {
                 var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
@@ -195,6 +231,11 @@ namespace Content.Server.Connection
             return null;
         }
 
+        private bool HasTemporaryBypass(NetUserId user)
+        {
+            return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
+        }
+
         private async Task<NetUserId?> AssignUserIdCallback(string name)
         {
             if (!_cfg.GetCVar(CCVars.GamePersistGuests))
diff --git a/Content.Server/Connection/GrantConnectBypassCommand.cs b/Content.Server/Connection/GrantConnectBypassCommand.cs
new file mode 100644 (file)
index 0000000..e2d0d73
--- /dev/null
@@ -0,0 +1,60 @@
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Robust.Shared.Console;
+
+namespace Content.Server.Connection;
+
+[AdminCommand(AdminFlags.Admin)]
+public sealed class GrantConnectBypassCommand : LocalizedCommands
+{
+    private static readonly TimeSpan DefaultDuration = TimeSpan.FromHours(1);
+
+    [Dependency] private readonly IPlayerLocator _playerLocator = default!;
+    [Dependency] private readonly IConnectionManager _connectionManager = default!;
+
+    public override string Command => "grant_connect_bypass";
+
+    public override async void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        if (args.Length is not (1 or 2))
+        {
+            shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-invalid-args"));
+            return;
+        }
+
+        var argPlayer = args[0];
+        var info = await _playerLocator.LookupIdByNameOrIdAsync(argPlayer);
+        if (info == null)
+        {
+            shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-unknown-user", ("user", argPlayer)));
+            return;
+        }
+
+        var duration = DefaultDuration;
+        if (args.Length > 1)
+        {
+            var argDuration = args[2];
+            if (!uint.TryParse(argDuration, out var minutes))
+            {
+                shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-invalid-duration", ("duration", argDuration)));
+                return;
+            }
+
+            duration = TimeSpan.FromMinutes(minutes);
+        }
+
+        _connectionManager.AddTemporaryConnectBypass(info.UserId, duration);
+        shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-success", ("user", argPlayer)));
+    }
+
+    public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+            return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-user"));
+
+        if (args.Length == 2)
+            return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-duration"));
+
+        return CompletionResult.Empty;
+    }
+}
diff --git a/Resources/Locale/en-US/administration/commands/connection-commands.ftl b/Resources/Locale/en-US/administration/commands/connection-commands.ftl
new file mode 100644 (file)
index 0000000..6699104
--- /dev/null
@@ -0,0 +1,16 @@
+## Strings for the "grant_connect_bypass" command.
+
+cmd-grant_connect_bypass-desc = Temporarily allow a user to bypass regular connection checks.
+cmd-grant_connect_bypass-help = Usage: grant_connect_bypass <user> [duration minutes]
+    Temporarily grants a user the ability to bypass regular connections restrictions.
+    The bypass only applies to this game server and will expire after (by default) 1 hour.
+    They will be able to join regardless of whitelist, panic bunker, or player cap.
+
+cmd-grant_connect_bypass-arg-user = <user>
+cmd-grant_connect_bypass-arg-duration = [duration minutes]
+
+cmd-grant_connect_bypass-invalid-args = Expected 1 or 2 arguments
+cmd-grant_connect_bypass-unknown-user = Unable to find user '{$user}'
+cmd-grant_connect_bypass-invalid-duration = Invalid duration '{$duration}'
+
+cmd-grant_connect_bypass-success = Successfully added bypass for user '{$user}'