1
0

PlayerLocator.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using System.Collections.Immutable;
  2. using System.Net;
  3. using System.Net.Http;
  4. using System.Net.Http.Headers;
  5. using System.Net.Http.Json;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Content.Server.Connection;
  9. using Content.Server.Database;
  10. using Content.Shared.Database;
  11. using JetBrains.Annotations;
  12. using Robust.Server.Player;
  13. using Robust.Shared;
  14. using Robust.Shared.Configuration;
  15. using Robust.Shared.Network;
  16. using Robust.Shared.Player;
  17. namespace Content.Server.Administration
  18. {
  19. /// <summary>
  20. /// Contains data resolved via <see cref="IPlayerLocator"/>.
  21. /// </summary>
  22. /// <param name="UserId">The ID of the located user.</param>
  23. /// <param name="LastAddress">The last known IP address that the user connected with.</param>
  24. /// <param name="LastHWId">
  25. /// The last known HWID that the user connected with.
  26. /// This should be used for placing new records involving HWIDs, such as bans.
  27. /// For looking up data based on HWID, use combined <see cref="LastLegacyHWId"/> and <see cref="LastModernHWIds"/>.
  28. /// </param>
  29. /// <param name="Username">The last known username for the user connected with.</param>
  30. /// <param name="LastLegacyHWId">
  31. /// The last known legacy HWID value this user connected with. Only use for old lookups!
  32. /// </param>
  33. /// <param name="LastModernHWIds">
  34. /// The set of last known modern HWIDs the user connected with.
  35. /// </param>
  36. public sealed record LocatedPlayerData(
  37. NetUserId UserId,
  38. IPAddress? LastAddress,
  39. ImmutableTypedHwid? LastHWId,
  40. string Username,
  41. ImmutableArray<byte>? LastLegacyHWId,
  42. ImmutableArray<ImmutableArray<byte>> LastModernHWIds);
  43. /// <summary>
  44. /// Utilities for finding user IDs that extend to more than the server database.
  45. /// </summary>
  46. /// <remarks>
  47. /// Methods in this class will check connected clients, server database
  48. /// AND the authentication server for lookups, in that order.
  49. /// </remarks>
  50. public interface IPlayerLocator
  51. {
  52. /// <summary>
  53. /// Look up a user ID by name globally.
  54. /// </summary>
  55. /// <returns>Null if the player does not exist.</returns>
  56. Task<LocatedPlayerData?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default);
  57. /// <summary>
  58. /// If passed a GUID, looks up the ID and tries to find HWId for it.
  59. /// If passed a player name, returns <see cref="LookupIdByNameAsync"/>.
  60. /// </summary>
  61. Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default);
  62. /// <summary>
  63. /// Look up a user by <see cref="NetUserId"/> globally.
  64. /// </summary>
  65. /// <returns>Null if the player does not exist.</returns>
  66. Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default);
  67. }
  68. internal sealed class PlayerLocator : IPlayerLocator, IDisposable, IPostInjectInit
  69. {
  70. [Dependency] private readonly IPlayerManager _playerManager = default!;
  71. [Dependency] private readonly IConfigurationManager _configurationManager = default!;
  72. [Dependency] private readonly IServerDbManager _db = default!;
  73. [Dependency] private readonly ILogManager _logManager = default!;
  74. private readonly HttpClient _httpClient = new();
  75. private ISawmill _sawmill = default!;
  76. public PlayerLocator()
  77. {
  78. if (typeof(PlayerLocator).Assembly.GetName().Version is { } version)
  79. {
  80. _httpClient.DefaultRequestHeaders.UserAgent.Add(
  81. new ProductInfoHeaderValue("SpaceStation14", version.ToString()));
  82. }
  83. }
  84. public async Task<LocatedPlayerData?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default)
  85. {
  86. // Check people currently on the server, the easiest case.
  87. if (_playerManager.TryGetSessionByUsername(playerName, out var session))
  88. return ReturnForSession(session);
  89. // Check database for past players.
  90. var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
  91. if (record != null)
  92. return ReturnForPlayerRecord(record);
  93. // If all else fails, ask the auth server.
  94. var authServer = _configurationManager.GetCVar(CVars.AuthServer);
  95. var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}";
  96. using var resp = await _httpClient.GetAsync(requestUri, cancel);
  97. return await HandleAuthServerResponse(resp, cancel);
  98. }
  99. public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
  100. {
  101. // Check people currently on the server, the easiest case.
  102. if (_playerManager.TryGetSessionById(userId, out var session))
  103. return ReturnForSession(session);
  104. // Check database for past players.
  105. var record = await _db.GetPlayerRecordByUserId(userId, cancel);
  106. if (record != null)
  107. return ReturnForPlayerRecord(record);
  108. // If all else fails, ask the auth server.
  109. var authServer = _configurationManager.GetCVar(CVars.AuthServer);
  110. var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
  111. using var resp = await _httpClient.GetAsync(requestUri, cancel);
  112. return await HandleAuthServerResponse(resp, cancel);
  113. }
  114. private async Task<LocatedPlayerData?> HandleAuthServerResponse(HttpResponseMessage resp, CancellationToken cancel)
  115. {
  116. if (resp.StatusCode == HttpStatusCode.NotFound)
  117. return null;
  118. if (!resp.IsSuccessStatusCode)
  119. {
  120. _sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode);
  121. return null;
  122. }
  123. var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
  124. if (responseData == null)
  125. {
  126. _sawmill.Error("Auth server returned null response!");
  127. return null;
  128. }
  129. return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName, null, []);
  130. }
  131. private static LocatedPlayerData ReturnForSession(ICommonSession session)
  132. {
  133. var userId = session.UserId;
  134. var address = session.Channel.RemoteEndPoint.Address;
  135. var hwId = session.Channel.UserData.GetModernHwid();
  136. return new LocatedPlayerData(
  137. userId,
  138. address,
  139. hwId,
  140. session.Name,
  141. session.Channel.UserData.HWId,
  142. session.Channel.UserData.ModernHWIds);
  143. }
  144. private static LocatedPlayerData ReturnForPlayerRecord(PlayerRecord record)
  145. {
  146. var hwid = record.HWId;
  147. return new LocatedPlayerData(
  148. record.UserId,
  149. record.LastSeenAddress,
  150. hwid,
  151. record.LastSeenUserName,
  152. hwid is { Type: HwidType.Legacy } ? hwid.Hwid : null,
  153. hwid is { Type: HwidType.Modern } ? [hwid.Hwid] : []);
  154. }
  155. public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
  156. {
  157. if (Guid.TryParse(playerName, out var guid))
  158. {
  159. var userId = new NetUserId(guid);
  160. return await LookupIdAsync(userId, cancel);
  161. }
  162. return await LookupIdByNameAsync(playerName, cancel);
  163. }
  164. [UsedImplicitly]
  165. private sealed record UserDataResponse(string UserName, Guid UserId)
  166. {
  167. }
  168. void IDisposable.Dispose()
  169. {
  170. _httpClient.Dispose();
  171. }
  172. void IPostInjectInit.PostInject()
  173. {
  174. _sawmill = _logManager.GetSawmill("PlayerLocate");
  175. }
  176. }
  177. }