diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs index 1f3ed03bb9..89c47a8aeb 100644 --- a/Content.Client/Administration/Managers/ClientAdminManager.cs +++ b/Content.Client/Administration/Managers/ClientAdminManager.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Reflection; using Content.Shared.Administration; using Robust.Client.Console; -using Robust.Shared.Console; -using Robust.Shared.IoC; -using Robust.Shared.Log; +using Robust.Shared.ContentPack; using Robust.Shared.Network; -using Robust.Shared.Reflection; +using Robust.Shared.Utility; namespace Content.Client.Administration.Managers { @@ -15,10 +10,13 @@ namespace Content.Client.Administration.Managers { [Dependency] private readonly IClientNetManager _netMgr = default!; [Dependency] private readonly IClientConGroupController _conGroup = default!; + [Dependency] private readonly IResourceManager _res = default!; private AdminData? _adminData; private readonly HashSet _availableCommands = new(); + private readonly AdminCommandPermissions _localCommandPermissions = new(); + public event Action? AdminStatusUpdated; public bool IsActive() @@ -33,6 +31,16 @@ namespace Content.Client.Administration.Managers public bool CanCommand(string cmdName) { + if (_adminData != null && _adminData.HasFlag(AdminFlags.Host)) + { + // Host can execute all commands when connected. + // Kind of a shortcut to avoid pains during development. + return true; + } + + if (_localCommandPermissions.CanCommand(cmdName, _adminData)) + return true; + return _availableCommands.Contains(cmdName); } @@ -59,6 +67,12 @@ namespace Content.Client.Administration.Managers public void Initialize() { _netMgr.RegisterNetMessage(UpdateMessageRx); + + // Load flags for engine commands, since those don't have the attributes. + if (_res.TryContentFileRead(new ResourcePath("/clientCommandPerms.yml"), out var efs)) + { + _localCommandPermissions.LoadPermissionsFromStream(efs); + } } private void UpdateMessageRx(MsgUpdateAdminStatus message) diff --git a/Content.Server/Administration/Managers/AdminManager.cs b/Content.Server/Administration/Managers/AdminManager.cs index 4391f92ae2..97b2af8f69 100644 --- a/Content.Server/Administration/Managers/AdminManager.cs +++ b/Content.Server/Administration/Managers/AdminManager.cs @@ -1,4 +1,3 @@ -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -15,7 +14,6 @@ using Robust.Shared.ContentPack; using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Utility; -using YamlDotNet.RepresentationModel; namespace Content.Server.Administration.Managers @@ -42,10 +40,7 @@ namespace Content.Server.Administration.Managers public IEnumerable AllAdmins => _admins.Select(p => p.Key); - // If a command isn't in this list it's server-console only. - // if a command is in but the flags value is null it's available to everybody. - private readonly HashSet _anyCommands = new(); - private readonly Dictionary _adminCommands = new(); + private readonly AdminCommandPermissions _commandPermissions = new(); public bool IsAdmin(IPlayerSession session, bool includeDeAdmin = false) { @@ -180,63 +175,19 @@ namespace Content.Server.Administration.Managers if (flagsReq.Length != 0) { - _adminCommands.Add(cmdName, flagsReq); + _commandPermissions.AdminCommands.Add(cmdName, flagsReq); } else { - _anyCommands.Add(cmdName); + _commandPermissions.AnyCommands.Add(cmdName); } } // Load flags for engine commands, since those don't have the attributes. if (_res.TryContentFileRead(new ResourcePath("/engineCommandPerms.yml"), out var efs)) { - LoadPermissionsFromStream(efs); + _commandPermissions.LoadPermissionsFromStream(efs); } - - // Load flags for client-only commands, those don't have the flag attributes, only "AnyCommand". - if (_res.TryContentFileRead(new ResourcePath("/clientCommandPerms.yml"), out var cfs)) - { - LoadPermissionsFromStream(cfs); - } - } - - private void LoadPermissionsFromStream(Stream fs) - { - using var reader = new StreamReader(fs, EncodingHelpers.UTF8); - var yStream = new YamlStream(); - yStream.Load(reader); - var root = (YamlSequenceNode) yStream.Documents[0].RootNode; - - foreach (var child in root) - { - var map = (YamlMappingNode) child; - var commands = map.GetNode("Commands").Select(p => p.AsString()); - if (map.TryGetNode("Flags", out var flagsNode)) - { - var flagNames = flagsNode.AsString().Split(",", StringSplitOptions.RemoveEmptyEntries); - var flags = AdminFlagsHelper.NamesToFlags(flagNames); - foreach (var cmd in commands) - { - if (!_adminCommands.TryGetValue(cmd, out var exFlags)) - { - _adminCommands.Add(cmd, new[] {flags}); - } - else - { - var newArr = new AdminFlags[exFlags.Length + 1]; - exFlags.CopyTo(newArr, 0); - exFlags[^1] = flags; - _adminCommands[cmd] = newArr; - } - } - } - else - { - _anyCommands.UnionWith(commands); - } - } - } public void PromoteHost(IPlayerSession player) @@ -257,13 +208,13 @@ namespace Content.Server.Administration.Managers { var msg = new MsgUpdateAdminStatus(); - var commands = new List(_anyCommands); + var commands = new List(_commandPermissions.AnyCommands); if (_admins.TryGetValue(session, out var adminData)) { msg.Admin = adminData.Data; - commands.AddRange(_adminCommands + commands.AddRange(_commandPermissions.AdminCommands .Where(p => p.Value.Any(f => adminData.Data.HasFlag(f))) .Select(p => p.Key)); } @@ -400,13 +351,13 @@ namespace Content.Server.Administration.Managers public bool CanCommand(IPlayerSession session, string cmdName) { - if (_anyCommands.Contains(cmdName)) + if (_commandPermissions.AnyCommands.Contains(cmdName)) { // Anybody can use this command. return true; } - if (!_adminCommands.TryGetValue(cmdName, out var flagsReq)) + if (!_commandPermissions.AdminCommands.TryGetValue(cmdName, out var flagsReq)) { // Server-console only. return false; diff --git a/Content.Shared/Administration/AdminCommandPermissions.cs b/Content.Shared/Administration/AdminCommandPermissions.cs new file mode 100644 index 0000000000..e5cea79e1b --- /dev/null +++ b/Content.Shared/Administration/AdminCommandPermissions.cs @@ -0,0 +1,83 @@ +using System.IO; +using System.Linq; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Administration; + +public sealed class AdminCommandPermissions +{ + // Commands executable by anybody. + public readonly HashSet AnyCommands = new(); + + // Commands only executable by admins with one of the given flag masks. + public readonly Dictionary AdminCommands = new(); + + public void LoadPermissionsFromStream(Stream fs) + { + using var reader = new StreamReader(fs, EncodingHelpers.UTF8); + var yStream = new YamlStream(); + yStream.Load(reader); + var root = (YamlSequenceNode) yStream.Documents[0].RootNode; + + foreach (var child in root) + { + var map = (YamlMappingNode) child; + var commands = map.GetNode("Commands").Select(p => p.AsString()); + if (map.TryGetNode("Flags", out var flagsNode)) + { + var flagNames = flagsNode.AsString().Split(",", StringSplitOptions.RemoveEmptyEntries); + var flags = AdminFlagsHelper.NamesToFlags(flagNames); + foreach (var cmd in commands) + { + if (!AdminCommands.TryGetValue(cmd, out var exFlags)) + { + AdminCommands.Add(cmd, new[] {flags}); + } + else + { + var newArr = new AdminFlags[exFlags.Length + 1]; + exFlags.CopyTo(newArr, 0); + exFlags[^1] = flags; + AdminCommands[cmd] = newArr; + } + } + } + else + { + AnyCommands.UnionWith(commands); + } + } + } + + public bool CanCommand(string cmdName, AdminData? admin) + { + if (AnyCommands.Contains(cmdName)) + { + // Anybody can use this command. + return true; + } + + if (!AdminCommands.TryGetValue(cmdName, out var flagsReq)) + { + // Server-console only. + return false; + } + + if (admin == null) + { + // Player isn't an admin. + return false; + } + + foreach (var flagReq in flagsReq) + { + if (admin.HasFlag(flagReq)) + { + return true; + } + } + + return false; + } +} diff --git a/Resources/clientCommandPerms.yml b/Resources/clientCommandPerms.yml index 363aaaf5cf..a816953a4f 100644 --- a/Resources/clientCommandPerms.yml +++ b/Resources/clientCommandPerms.yml @@ -1,3 +1,31 @@ +# Available to everybody +- Commands: + - disconnect + - help + - list + - quit + - hardquit + - svbind + - bind + - exec # macro moment + - cls + - vram + - monitor + - setmonitor + - keyinfo + - setclipboard + - getclipboard + - net_graph + - net_watchent + - net_draw_interp + - devwindow + - fill + - dumpentities + - ">" + - gcf + - gc + - gc_mode + - Flags: DEBUG Commands: - atvrange diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 3563a7fe0c..cc887175ae 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -1,32 +1,3 @@ -# Available to everybody -- Commands: - - disconnect - - help - - list - - quit - - hardquit - - cvar - - svbind - - bind - - exec # macro moment - - clear - - vram - - monitor - - setmonitor - - keyinfo - - setclipboard - - getclipboard - - gcf - - net_graph - - net_watchent - - net_draw_interp - - devwindow - - fill - - dumpentities - - getcomponentregistration - - fuck - - ">" - - Flags: VAREDIT Commands: - addcomp @@ -141,6 +112,10 @@ - scsi - csi - lsasm + - cvar + - gcf + - getcomponentregistration + - fuck - Flags: QUERY Commands: