ConGroups are gone. Long live admin flags in content.
This commit is contained in:
377
Content.Server/Administration/AdminManager.cs
Normal file
377
Content.Server/Administration/AdminManager.cs
Normal file
@@ -0,0 +1,377 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Network.NetMessages;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Resources;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Server.Administration
|
||||
{
|
||||
public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IServerNetManager _netMgr = default!;
|
||||
[Dependency] private readonly IConGroupController _conGroup = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly IConsoleShell _consoleShell = default!;
|
||||
|
||||
private readonly Dictionary<IPlayerSession, AdminReg> _admins = new Dictionary<IPlayerSession, AdminReg>();
|
||||
|
||||
public IEnumerable<IPlayerSession> ActiveAdmins => _admins
|
||||
.Where(p => p.Value.Data.Active)
|
||||
.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<string> _anyCommands = new HashSet<string>();
|
||||
private readonly Dictionary<string, AdminFlags[]> _adminCommands = new Dictionary<string, AdminFlags[]>();
|
||||
|
||||
public AdminData? GetAdminData(IPlayerSession session, bool includeDeAdmin = false)
|
||||
{
|
||||
if (_admins.TryGetValue(session, out var reg) && (reg.Data.Active || includeDeAdmin))
|
||||
{
|
||||
return reg.Data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void DeAdmin(IPlayerSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
{
|
||||
throw new ArgumentException($"Player {session} is not an admin");
|
||||
}
|
||||
|
||||
if (!reg.Data.Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var plyData = session.ContentData()!;
|
||||
plyData.ExplicitlyDeadminned = true;
|
||||
reg.Data.Active = false;
|
||||
|
||||
// TODO: Send messages to all admins.
|
||||
|
||||
UpdateAdminStatus(session);
|
||||
}
|
||||
|
||||
public void ReAdmin(IPlayerSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
{
|
||||
throw new ArgumentException($"Player {session} is not an admin");
|
||||
}
|
||||
|
||||
var plyData = session.ContentData()!;
|
||||
plyData.ExplicitlyDeadminned = true;
|
||||
reg.Data.Active = true;
|
||||
|
||||
// TODO: Send messages to all admins.
|
||||
|
||||
UpdateAdminStatus(session);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netMgr.RegisterNetMessage<MsgUpdateAdminStatus>(MsgUpdateAdminStatus.NAME);
|
||||
|
||||
// Cache permissions for loaded console commands with the requisite attributes.
|
||||
foreach (var (cmdName, cmd) in _consoleShell.AvailableCommands)
|
||||
{
|
||||
var (isAvail, flagsReq) = GetRequiredFlag(cmd);
|
||||
|
||||
if (!isAvail)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flagsReq.Length != 0)
|
||||
{
|
||||
_adminCommands.Add(cmdName, flagsReq);
|
||||
}
|
||||
else
|
||||
{
|
||||
_anyCommands.Add(cmdName);
|
||||
}
|
||||
}
|
||||
|
||||
// Load flags for engine commands, since those don't have the attributes.
|
||||
if (_res.TryContentFileRead(new ResourcePath("/engineCommandPerms.yml"), out var 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<YamlSequenceNode>("Commands").Select(p => p.AsString());
|
||||
if (map.TryGetNode("Flags", out var flagsNode))
|
||||
{
|
||||
var flagNames = flagsNode.AsString().Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||
var flags = AdminFlagsExt.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
_conGroup.Implementation = this;
|
||||
}
|
||||
|
||||
// NOTE: Also sends commands list for non admins..
|
||||
private void UpdateAdminStatus(IPlayerSession session)
|
||||
{
|
||||
var msg = _netMgr.CreateNetMessage<MsgUpdateAdminStatus>();
|
||||
|
||||
var commands = new List<string>(_anyCommands);
|
||||
|
||||
if (_admins.TryGetValue(session, out var adminData))
|
||||
{
|
||||
msg.Admin = adminData.Data;
|
||||
|
||||
commands.AddRange(_adminCommands
|
||||
.Where(p => p.Value.Any(f => adminData.Data.HasFlag(f)))
|
||||
.Select(p => p.Key));
|
||||
}
|
||||
|
||||
msg.AvailableCommands = commands.ToArray();
|
||||
|
||||
_netMgr.ServerSendMessage(msg, session.ConnectedClient);
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Connected)
|
||||
{
|
||||
// Run this so that available commands list gets sent.
|
||||
UpdateAdminStatus(e.Session);
|
||||
}
|
||||
else if (e.NewStatus == SessionStatus.InGame)
|
||||
{
|
||||
LoginAdminMaybe(e.Session);
|
||||
}
|
||||
else if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
_admins.Remove(e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private async void LoginAdminMaybe(IPlayerSession session)
|
||||
{
|
||||
AdminReg reg;
|
||||
if (IsLocal(session) && _cfg.GetCVar(CCVars.ConsoleLoginLocal))
|
||||
{
|
||||
var data = new AdminData
|
||||
{
|
||||
Title = Loc.GetString("Host"),
|
||||
Flags = AdminFlagsExt.Everything,
|
||||
};
|
||||
|
||||
reg = new AdminReg(session, data)
|
||||
{
|
||||
IsSpecialLogin = true,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var dbData = await _dbManager.GetAdminDataForAsync(session.UserId);
|
||||
|
||||
if (dbData == null)
|
||||
{
|
||||
// Not an admin!
|
||||
return;
|
||||
}
|
||||
|
||||
var flags = AdminFlags.None;
|
||||
|
||||
if (dbData.AdminRank != null)
|
||||
{
|
||||
flags = AdminFlagsExt.NamesToFlags(dbData.AdminRank.Flags.Select(p => p.Flag));
|
||||
}
|
||||
|
||||
foreach (var dbFlag in dbData.Flags)
|
||||
{
|
||||
var flag = AdminFlagsExt.NameToFlag(dbFlag.Flag);
|
||||
if (dbFlag.Negative)
|
||||
{
|
||||
flags &= ~flag;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
var data = new AdminData
|
||||
{
|
||||
Flags = flags
|
||||
};
|
||||
|
||||
if (dbData.Title != null)
|
||||
{
|
||||
data.Title = dbData.Title;
|
||||
}
|
||||
else if (dbData.AdminRank != null)
|
||||
{
|
||||
data.Title = dbData.AdminRank.Name;
|
||||
}
|
||||
|
||||
reg = new AdminReg(session, data);
|
||||
}
|
||||
|
||||
_admins.Add(session, reg);
|
||||
|
||||
if (!session.ContentData()!.ExplicitlyDeadminned)
|
||||
{
|
||||
reg.Data.Active = true;
|
||||
}
|
||||
|
||||
UpdateAdminStatus(session);
|
||||
}
|
||||
|
||||
private static bool IsLocal(IPlayerSession player)
|
||||
{
|
||||
var ep = player.ConnectedClient.RemoteEndPoint;
|
||||
var addr = ep.Address;
|
||||
if (addr.IsIPv4MappedToIPv6)
|
||||
{
|
||||
addr = addr.MapToIPv4();
|
||||
}
|
||||
|
||||
return Equals(addr, IPAddress.Loopback) || Equals(addr, IPAddress.IPv6Loopback);
|
||||
}
|
||||
|
||||
public bool CanCommand(IPlayerSession session, string cmdName)
|
||||
{
|
||||
if (_anyCommands.Contains(cmdName))
|
||||
{
|
||||
// Anybody can use this command.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_adminCommands.TryGetValue(cmdName, out var flagsReq))
|
||||
{
|
||||
// Server-console only.
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = GetAdminData(session);
|
||||
if (data == null)
|
||||
{
|
||||
// Player isn't an admin.
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var flagReq in flagsReq)
|
||||
{
|
||||
if (data.HasFlag(flagReq))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static (bool isAvail, AdminFlags[] flagsReq) GetRequiredFlag(IClientCommand cmd)
|
||||
{
|
||||
var type = cmd.GetType();
|
||||
if (Attribute.IsDefined(type, typeof(AnyCommandAttribute)))
|
||||
{
|
||||
// Available to everybody.
|
||||
return (true, Array.Empty<AdminFlags>());
|
||||
}
|
||||
|
||||
var attribs = type.GetCustomAttributes(typeof(AdminCommandAttribute))
|
||||
.Cast<AdminCommandAttribute>()
|
||||
.Select(p => p.Flags)
|
||||
.ToArray();
|
||||
|
||||
// If attribs.length == 0 then no access attribute is specified,
|
||||
// and this is a server-only command.
|
||||
return (attribs.Length != 0, attribs);
|
||||
}
|
||||
|
||||
public bool CanViewVar(IPlayerSession session)
|
||||
{
|
||||
return GetAdminData(session)?.CanViewVar() ?? false;
|
||||
}
|
||||
|
||||
public bool CanAdminPlace(IPlayerSession session)
|
||||
{
|
||||
return GetAdminData(session)?.CanAdminPlace() ?? false;
|
||||
}
|
||||
|
||||
public bool CanScript(IPlayerSession session)
|
||||
{
|
||||
return GetAdminData(session)?.CanScript() ?? false;
|
||||
}
|
||||
|
||||
public bool CanAdminMenu(IPlayerSession session)
|
||||
{
|
||||
return GetAdminData(session)?.CanAdminMenu() ?? false;
|
||||
}
|
||||
|
||||
private sealed class AdminReg
|
||||
{
|
||||
public IPlayerSession Session;
|
||||
|
||||
public AdminData Data;
|
||||
|
||||
// Such as console.loginlocal
|
||||
// Means that stuff like permissions editing is blocked.
|
||||
public bool IsSpecialLogin;
|
||||
|
||||
public AdminReg(IPlayerSession session, AdminData data)
|
||||
{
|
||||
Data = data;
|
||||
Session = session;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user