diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index f23b4ada45..fd80bc99fe 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -5,7 +5,6 @@ using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.IoC; -using Robust.Shared.Network; #nullable enable @@ -16,12 +15,13 @@ namespace Content.Server.Administration.Commands { public string Command => "ban"; public string Description => "Bans somebody"; - public string Help => $"Usage: {Command} "; + public string Help => $"Usage: {Command} [duration in minutes, leave out or 0 for permanent ban]"; public async void Execute(IConsoleShell shell, string argStr, string[] args) { var player = shell.Player as IPlayerSession; var plyMgr = IoCManager.Resolve(); + var locator = IoCManager.Resolve(); var dbMan = IoCManager.Resolve(); string target; @@ -51,22 +51,15 @@ namespace Content.Server.Administration.Commands return; } - NetUserId targetUid; - - if (plyMgr.TryGetSessionByUsername(target, out var targetSession)) + var resolvedUid = await locator.LookupIdByNameOrIdAsync(target); + if (resolvedUid == null) { - targetUid = targetSession.UserId; - } - else if (Guid.TryParse(target, out var targetGuid)) - { - targetUid = new NetUserId(targetGuid); - } - else - { - shell.WriteLine("Unable to find user with that name."); + shell.WriteError("Unable to find a player with that name."); return; } + var targetUid = resolvedUid.Value; + if (player != null && player.UserId == targetUid) { shell.WriteLine("You can't ban yourself!"); @@ -81,7 +74,7 @@ namespace Content.Server.Administration.Commands await dbMan.AddServerBanAsync(new ServerBanDef(null, targetUid, null, DateTimeOffset.Now, expires, reason, player?.UserId, null)); - var response = new StringBuilder($"Banned {targetUid} with reason \"{reason}\""); + var response = new StringBuilder($"Banned {target} with reason \"{reason}\""); response.Append(expires == null ? " permanently." diff --git a/Content.Server/Administration/PlayerLocator.cs b/Content.Server/Administration/PlayerLocator.cs new file mode 100644 index 0000000000..84aa23483a --- /dev/null +++ b/Content.Server/Administration/PlayerLocator.cs @@ -0,0 +1,135 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Database; +using JetBrains.Annotations; +using Robust.Server.Player; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Network; + +namespace Content.Server.Administration +{ + /// + /// Utilities for finding user IDs that extend to more than the server database. + /// + /// + /// Methods in this class will check connected clients, server database + /// AND the authentication server for lookups, in that order. + /// + public interface IPlayerLocator + { + /// + /// Look up a user ID by name globally. + /// + /// Null if the player does not exist. + Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default); + + /// + /// If passed a GUID, runs and only returns it if the account exists. + /// If passed a player name, returns . + /// + Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default); + + /// + /// Checks whether the specified user ID is an existing account, globally. + /// + /// True if the player account exists, false otherwise + Task DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default); + } + + internal sealed class PlayerLocator : IPlayerLocator + { + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IServerDbManager _db = default!; + + public async Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default) + { + // Check people currently on the server, the easiest case. + if (_playerManager.TryGetSessionByUsername(playerName, out var session)) + return session.UserId; + + // Check database for past players. + var record = await _db.GetPlayerRecordByUserName(playerName, cancel); + if (record != null) + return record.UserId; + + // If all else fails, ask the auth server. + var client = new HttpClient(); + var authServer = _configurationManager.GetCVar(CVars.AuthServer); + var resp = await client.GetAsync($"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}", + cancel); + + if (resp.StatusCode == HttpStatusCode.NotFound) + return null; + + if (!resp.IsSuccessStatusCode) + { + Logger.ErrorS("PlayerLocate", "Auth server returned bad response {StatusCode}!", resp.StatusCode); + return null; + } + + var responseData = await resp.Content.ReadFromJsonAsync(cancellationToken: cancel); + + if (responseData == null) + { + Logger.ErrorS("PlayerLocate", "Auth server returned null response!"); + return null; + } + + return new NetUserId(responseData.UserId); + } + + public async Task DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default) + { + // Check people currently on the server, the easiest case. + if (_playerManager.ValidSessionId(userId)) + return true; + + // Check database for past players. + var record = await _db.GetPlayerRecordByUserId(userId, cancel); + if (record != null) + return true; + + // If all else fails, ask the auth server. + var client = new HttpClient(); + var authServer = _configurationManager.GetCVar(CVars.AuthServer); + var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}"; + var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, requestUri), cancel); + + if (resp.StatusCode == HttpStatusCode.NotFound) + return false; + + if (!resp.IsSuccessStatusCode) + { + Logger.ErrorS("PlayerLocate", "Auth server returned bad response {StatusCode}!", resp.StatusCode); + return false; + } + + return true; + } + + public async Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) + { + if (Guid.TryParse(playerName, out var guid)) + { + var userId = new NetUserId(guid); + + return await DoesPlayerExistAsync(userId, cancel) ? userId : null; + } + + return await LookupIdByNameAsync(playerName, cancel); + } + + [UsedImplicitly] + private sealed record UserDataResponse(string UserName, Guid UserId) + { + } + } +} diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index 37c09b73a4..67a39ef973 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -62,6 +62,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } }