| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711 |
- using System.Diagnostics;
- using System.Linq;
- using System.Reflection;
- using System.Threading.Tasks;
- using Content.Server.Chat.Managers;
- using Content.Server.Database;
- using Content.Server.Players;
- using Content.Shared.Administration;
- using Content.Shared.CCVar;
- using Content.Shared.Info;
- using Content.Shared.Players;
- using Robust.Server.Console;
- using Robust.Server.Player;
- using Robust.Shared.Configuration;
- using Robust.Shared.Console;
- using Robust.Shared.ContentPack;
- using Robust.Shared.Enums;
- using Robust.Shared.Network;
- using Robust.Shared.Player;
- using Robust.Shared.Toolshed;
- using Robust.Shared.Toolshed.Errors;
- using Robust.Shared.Utility;
- namespace Content.Server.Administration.Managers
- {
- public sealed partial 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 IServerConsoleHost _consoleHost = default!;
- [Dependency] private readonly IChatManager _chat = default!;
- [Dependency] private readonly ToolshedManager _toolshed = default!;
- [Dependency] private readonly ILogManager _logManager = default!;
- private readonly Dictionary<ICommonSession, AdminReg> _admins = new();
- private readonly HashSet<NetUserId> _promotedPlayers = new();
- public event Action<AdminPermsChangedEventArgs>? OnPermsChanged;
- public IEnumerable<ICommonSession> ActiveAdmins => _admins
- .Where(p => p.Value.Data.Active)
- .Select(p => p.Key);
- public IEnumerable<ICommonSession> AllAdmins => _admins.Select(p => p.Key);
- private readonly AdminCommandPermissions _commandPermissions = new();
- private readonly AdminCommandPermissions _toolshedCommandPermissions = new();
- private ISawmill _sawmill = default!;
- public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false)
- {
- return GetAdminData(session, includeDeAdmin) != null;
- }
- public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
- {
- if (_admins.TryGetValue(session, out var reg) && (reg.Data.Active || includeDeAdmin))
- {
- return reg.Data;
- }
- return null;
- }
- public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
- {
- if (_playerManager.TryGetSessionByEntity(uid, out var session))
- return GetAdminData(session, includeDeAdmin);
- return null;
- }
- public void DeAdmin(ICommonSession session)
- {
- if (!_admins.TryGetValue(session, out var reg))
- {
- throw new ArgumentException($"Player {session} is not an admin");
- }
- if (!reg.Data.Active)
- {
- return;
- }
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)));
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-normal-player-message"));
- UpdateDatabaseDeadminnedState(session, true);
- reg.Data.Active = false;
- SendPermsChangedEvent(session);
- UpdateAdminStatus(session);
- }
- private async void UpdateDatabaseDeadminnedState(ICommonSession player, bool newState)
- {
- try
- {
- // NOTE: This function gets called if you deadmin/readmin from a transient admin status.
- // (e.g. loginlocal)
- // In which case there may not be a database record.
- // The DB function handles this scenario fine, but it's worth noting.
- await _dbManager.UpdateAdminDeadminnedAsync(player.UserId, newState);
- }
- catch (Exception e)
- {
- _sawmill.Error("Failed to save deadmin state to database for {Admin}", player.UserId);
- }
- }
- public void Stealth(ICommonSession session)
- {
- if (!_admins.TryGetValue(session, out var reg))
- {
- throw new ArgumentException($"Player {session} is not an admin");
- }
- if (reg.Data.Stealth)
- return;
- var playerData = session.ContentData()!;
- playerData.Stealthed = true;
- reg.Data.Stealth = true;
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)), AdminFlags.Stealth);
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-enable-stealth", ("stealthAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
- }
- public void UnStealth(ICommonSession session)
- {
- if (!_admins.TryGetValue(session, out var reg))
- {
- throw new ArgumentException($"Player {session} is not an admin");
- }
- if (!reg.Data.Stealth)
- return;
- var playerData = session.ContentData()!;
- playerData.Stealthed = false;
- reg.Data.Stealth = false;
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-unstealthed-message"));
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)), flagBlacklist: AdminFlags.Stealth);
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-disable-stealth", ("exStealthAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
- }
- public void ReAdmin(ICommonSession session)
- {
- if (!_admins.TryGetValue(session, out var reg))
- {
- throw new ArgumentException($"Player {session} is not an admin");
- }
- if (reg.Data.Active)
- {
- return;
- }
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-admin-message"));
- UpdateDatabaseDeadminnedState(session, false);
- reg.Data.Active = true;
- if (!reg.Data.Stealth)
- {
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)));
- }
- else
- {
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message",
- ("newAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
- }
- SendPermsChangedEvent(session);
- UpdateAdminStatus(session);
- }
- public async void ReloadAdmin(ICommonSession player)
- {
- var data = await LoadAdminData(player);
- var curAdmin = _admins.GetValueOrDefault(player);
- if (data == null && curAdmin == null)
- {
- // Wasn't admin before or after.
- return;
- }
- if (data == null)
- {
- // No longer admin.
- _admins.Remove(player);
- _chat.DispatchServerMessage(player, Loc.GetString("admin-manager-no-longer-admin-message"));
- }
- else
- {
- var (aData, rankId, special) = data.Value;
- if (curAdmin == null)
- {
- // Now an admin.
- var reg = new AdminReg(player, aData)
- {
- IsSpecialLogin = special,
- RankId = rankId
- };
- _admins.Add(player, reg);
- _chat.DispatchServerMessage(player, Loc.GetString("admin-manager-became-admin-message"));
- }
- else
- {
- // Perms changed.
- curAdmin.IsSpecialLogin = special;
- curAdmin.RankId = rankId;
- curAdmin.Data = aData;
- if (curAdmin.Data.Active)
- {
- aData.Active = true;
- _chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message"));
- }
- }
- if (player.ContentData()!.Stealthed)
- {
- aData.Stealth = true;
- }
- }
- SendPermsChangedEvent(player);
- UpdateAdminStatus(player);
- }
- public void ReloadAdminsWithRank(int rankId)
- {
- foreach (var dat in _admins.Values.Where(p => p.RankId == rankId).ToArray())
- {
- ReloadAdmin(dat.Session);
- }
- }
- public void Initialize()
- {
- _sawmill = _logManager.GetSawmill("admin");
- _netMgr.RegisterNetMessage<MsgUpdateAdminStatus>();
- // Cache permissions for loaded console commands with the requisite attributes.
- foreach (var (cmdName, cmd) in _consoleHost.AvailableCommands)
- {
- var (isAvail, flagsReq) = GetRequiredFlag(cmd);
- if (!isAvail)
- {
- continue;
- }
- if (flagsReq.Length != 0)
- {
- _commandPermissions.AdminCommands.Add(cmdName, flagsReq);
- }
- else
- {
- _commandPermissions.AnyCommands.Add(cmdName);
- }
- }
- foreach (var spec in _toolshed.DefaultEnvironment.AllCommands())
- {
- var (isAvail, flagsReq) = GetRequiredFlag(spec.Cmd);
- if (!isAvail)
- {
- continue;
- }
- if (flagsReq.Length != 0)
- {
- _toolshedCommandPermissions.AdminCommands.TryAdd(spec.Cmd.Name, flagsReq);
- }
- else
- {
- _toolshedCommandPermissions.AnyCommands.Add(spec.Cmd.Name);
- }
- }
- // Load flags for engine commands, since those don't have the attributes.
- if (_res.TryContentFileRead(new ResPath("/engineCommandPerms.yml"), out var efs))
- {
- _commandPermissions.LoadPermissionsFromStream(efs);
- }
- if (_res.TryContentFileRead(new ResPath("/toolshedEngineCommandPerms.yml"), out var toolshedPerms))
- {
- _toolshedCommandPermissions.LoadPermissionsFromStream(toolshedPerms);
- }
- _toolshed.ActivePermissionController = this;
- InitializeMetrics();
- }
- public void PromoteHost(ICommonSession player)
- {
- _promotedPlayers.Add(player.UserId);
- ReloadAdmin(player);
- }
- void IPostInjectInit.PostInject()
- {
- _playerManager.PlayerStatusChanged += PlayerStatusChanged;
- _conGroup.Implementation = this;
- }
- // NOTE: Also sends commands list for non admins..
- private void UpdateAdminStatus(ICommonSession session)
- {
- var msg = new MsgUpdateAdminStatus();
- var commands = new List<string>(_commandPermissions.AnyCommands);
- if (_admins.TryGetValue(session, out var adminData))
- {
- msg.Admin = adminData.Data;
- commands.AddRange(_commandPermissions.AdminCommands
- .Where(p => p.Value.Any(f => adminData.Data.HasFlag(f)))
- .Select(p => p.Key));
- }
- msg.AvailableCommands = commands.ToArray();
- _netMgr.ServerSendMessage(msg, session.Channel);
- }
- 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)
- {
- if (_admins.Remove(e.Session, out var reg ) && _cfg.GetCVar(CCVars.AdminAnnounceLogout))
- {
- if (reg.Data.Stealth)
- {
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-logout-message",
- ("name", e.Session.Name)), flagWhitelist: AdminFlags.Stealth);
- }
- else
- {
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-logout-message",
- ("name", e.Session.Name)));
- }
- }
- }
- }
- private async void LoginAdminMaybe(ICommonSession session)
- {
- var adminDat = await LoadAdminData(session);
- if (adminDat == null)
- {
- // Not an admin.
- return;
- }
- var (dat, rankId, specialLogin) = adminDat.Value;
- var reg = new AdminReg(session, dat)
- {
- IsSpecialLogin = specialLogin,
- RankId = rankId
- };
- _admins.Add(session, reg);
- if (session.ContentData()!.Stealthed)
- reg.Data.Stealth = true;
- if (reg.Data.Active)
- {
- if (_cfg.GetCVar(CCVars.AdminAnnounceLogin))
- {
- if (reg.Data.Stealth)
- {
- _chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-login-message",
- ("name", session.Name)), flagWhitelist: AdminFlags.Stealth);
- }
- else
- {
- _chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-login-message",
- ("name", session.Name)));
- }
- }
- SendPermsChangedEvent(session);
- }
- UpdateAdminStatus(session);
- }
- private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminData(ICommonSession session)
- {
- var result = await LoadAdminDataCore(session);
- // Make sure admin didn't disconnect while data was loading.
- if (session.Status != SessionStatus.InGame)
- return null;
- return result;
- }
- private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminDataCore(ICommonSession session)
- {
- var promoteHost = IsLocal(session) && _cfg.GetCVar(CCVars.ConsoleLoginLocal)
- || _promotedPlayers.Contains(session.UserId)
- || session.Name == _cfg.GetCVar(CCVars.ConsoleLoginHostUser);
- if (promoteHost)
- {
- var data = new AdminData
- {
- Title = Loc.GetString("admin-manager-admin-data-host-title"),
- Flags = AdminFlagsHelper.Everything,
- Active = true,
- };
- return (data, null, true);
- }
- else
- {
- var dbData = await _dbManager.GetAdminDataForAsync(session.UserId);
- if (dbData == null)
- {
- // Not an admin!
- return null;
- }
- if (dbData.Suspended)
- {
- // Suspended admins don't count.
- return null;
- }
- var flags = AdminFlags.None;
- if (dbData.AdminRank != null)
- {
- flags = AdminFlagsHelper.NamesToFlags(dbData.AdminRank.Flags.Select(p => p.Flag));
- }
- foreach (var dbFlag in dbData.Flags)
- {
- var flag = AdminFlagsHelper.NameToFlag(dbFlag.Flag);
- if (dbFlag.Negative)
- {
- flags &= ~flag;
- }
- else
- {
- flags |= flag;
- }
- }
- var data = new AdminData
- {
- Flags = flags,
- Active = !dbData.Deadminned,
- };
- if (dbData.Title != null && _cfg.GetCVar(CCVars.AdminUseCustomNamesAdminRank))
- {
- data.Title = dbData.Title;
- }
- else if (dbData.AdminRank != null)
- {
- data.Title = dbData.AdminRank.Name;
- }
- return (data, dbData.AdminRankId, false);
- }
- }
- private static bool IsLocal(ICommonSession player)
- {
- var ep = player.Channel.RemoteEndPoint;
- var addr = ep.Address;
- if (addr.IsIPv4MappedToIPv6)
- {
- addr = addr.MapToIPv4();
- }
- return Equals(addr, System.Net.IPAddress.Loopback) || Equals(addr, System.Net.IPAddress.IPv6Loopback);
- }
- public bool TryGetCommandFlags(CommandSpec command, out AdminFlags[]? flags)
- {
- var cmdName = command.Cmd.Name;
- if (_toolshedCommandPermissions.AnyCommands.Contains(cmdName))
- {
- // Anybody can use this command.
- flags = null;
- return true;
- }
- if (_toolshedCommandPermissions.AdminCommands.TryGetValue(cmdName, out flags))
- {
- return true;
- }
- flags = null;
- return false;
- }
- public bool CanCommand(ICommonSession session, string cmdName)
- {
- if (_commandPermissions.AnyCommands.Contains(cmdName))
- {
- // Anybody can use this command.
- return true;
- }
- if (!_commandPermissions.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;
- }
- public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error)
- {
- if (user is null)
- {
- error = null;
- return true; // Server console.
- }
- var name = command.Cmd.Name;
- if (!TryGetCommandFlags(command, out var flags))
- {
- // Command is missing permissions.
- error = new CommandPermissionsUnassignedError(command);
- return false;
- }
- if (flags is null)
- {
- // Anyone can execute this.
- error = null;
- return true;
- }
- var data = GetAdminData(user);
- if (data == null)
- {
- // Player isn't an admin.
- error = new NoPermissionError(command);
- return false;
- }
- foreach (var flag in flags)
- {
- if (data.HasFlag(flag))
- {
- error = null;
- return true;
- }
- }
- error = new NoPermissionError(command);
- return false;
- }
- private static (bool isAvail, AdminFlags[] flagsReq) GetRequiredFlag(object cmd)
- {
- MemberInfo type = cmd.GetType();
- if (cmd is ConsoleHost.RegisteredCommand registered)
- {
- type = registered.Callback.Method;
- }
- 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(ICommonSession session)
- {
- return CanCommand(session, "vv");
- }
- public bool CanAdminPlace(ICommonSession session)
- {
- return GetAdminData(session)?.CanAdminPlace() ?? false;
- }
- public bool CanScript(ICommonSession session)
- {
- return GetAdminData(session)?.CanScript() ?? false;
- }
- public bool CanAdminMenu(ICommonSession session)
- {
- return GetAdminData(session)?.CanAdminMenu() ?? false;
- }
- public bool CanAdminReloadPrototypes(ICommonSession session)
- {
- return GetAdminData(session)?.CanAdminReloadPrototypes() ?? false;
- }
- private void SendPermsChangedEvent(ICommonSession session)
- {
- var flags = GetAdminData(session)?.Flags;
- OnPermsChanged?.Invoke(new AdminPermsChangedEventArgs(session, flags));
- }
- private sealed class AdminReg
- {
- public readonly ICommonSession Session;
- public AdminData Data;
- public int? RankId;
- // Such as console.loginlocal or promotehost
- public bool IsSpecialLogin;
- public AdminReg(ICommonSession session, AdminData data)
- {
- Data = data;
- Session = session;
- }
- }
- }
- }
- public record struct CommandPermissionsUnassignedError(CommandSpec Command) : IConError
- {
- public FormattedMessage DescribeInner()
- {
- return FormattedMessage.FromMarkupOrThrow($"The command {Command.FullName()} is missing permission flags and cannot be executed.");
- }
- public string? Expression { get; set; }
- public Vector2i? IssueSpan { get; set; }
- public StackTrace? Trace { get; set; }
- }
- public record struct NoPermissionError(CommandSpec Command) : IConError
- {
- public FormattedMessage DescribeInner()
- {
- return FormattedMessage.FromMarkupOrThrow($"You do not have permission to execute {Command.FullName()}");
- }
- public string? Expression { get; set; }
- public Vector2i? IssueSpan { get; set; }
- public StackTrace? Trace { get; set; }
- }
|