PermissionsEui.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. using System.Linq;
  2. using System.Threading.Tasks;
  3. using Content.Server.Administration.Managers;
  4. using Content.Server.Database;
  5. using Content.Server.EUI;
  6. using Content.Shared.Administration;
  7. using Content.Shared.Eui;
  8. using Robust.Server.Player;
  9. using Robust.Shared.Network;
  10. using DbAdminRank = Content.Server.Database.AdminRank;
  11. using static Content.Shared.Administration.PermissionsEuiMsg;
  12. namespace Content.Server.Administration.UI
  13. {
  14. public sealed class PermissionsEui : BaseEui
  15. {
  16. [Dependency] private readonly IPlayerManager _playerManager = default!;
  17. [Dependency] private readonly IServerDbManager _db = default!;
  18. [Dependency] private readonly IAdminManager _adminManager = default!;
  19. [Dependency] private readonly ILogManager _logManager = default!;
  20. private readonly ISawmill _sawmill;
  21. private bool _isLoading;
  22. private readonly List<(Admin a, string? lastUserName)> _admins = new List<(Admin, string? lastUserName)>();
  23. private readonly List<DbAdminRank> _adminRanks = new();
  24. public PermissionsEui()
  25. {
  26. IoCManager.InjectDependencies(this);
  27. _sawmill = _logManager.GetSawmill("admin.perms");
  28. }
  29. public override void Opened()
  30. {
  31. base.Opened();
  32. StateDirty();
  33. LoadFromDb();
  34. _adminManager.OnPermsChanged += AdminManagerOnPermsChanged;
  35. }
  36. public override void Closed()
  37. {
  38. base.Closed();
  39. _adminManager.OnPermsChanged -= AdminManagerOnPermsChanged;
  40. }
  41. private void AdminManagerOnPermsChanged(AdminPermsChangedEventArgs obj)
  42. {
  43. // Close UI if user loses +PERMISSIONS.
  44. if (obj.Player == Player && !UserAdminFlagCheck(AdminFlags.Permissions))
  45. {
  46. Close();
  47. }
  48. }
  49. public override EuiStateBase GetNewState()
  50. {
  51. if (_isLoading)
  52. {
  53. return new PermissionsEuiState
  54. {
  55. IsLoading = true
  56. };
  57. }
  58. return new PermissionsEuiState
  59. {
  60. Admins = _admins.Select(p => new PermissionsEuiState.AdminData
  61. {
  62. PosFlags = AdminFlagsHelper.NamesToFlags(p.a.Flags.Where(f => !f.Negative).Select(f => f.Flag)),
  63. NegFlags = AdminFlagsHelper.NamesToFlags(p.a.Flags.Where(f => f.Negative).Select(f => f.Flag)),
  64. Title = p.a.Title,
  65. RankId = p.a.AdminRankId,
  66. UserId = new NetUserId(p.a.UserId),
  67. UserName = p.lastUserName,
  68. Suspended = p.a.Suspended,
  69. }).ToArray(),
  70. AdminRanks = _adminRanks.ToDictionary(a => a.Id, a => new PermissionsEuiState.AdminRankData
  71. {
  72. Flags = AdminFlagsHelper.NamesToFlags(a.Flags.Select(p => p.Flag)),
  73. Name = a.Name
  74. })
  75. };
  76. }
  77. public override async void HandleMessage(EuiMessageBase msg)
  78. {
  79. base.HandleMessage(msg);
  80. switch (msg)
  81. {
  82. case AddAdmin ca:
  83. {
  84. await HandleCreateAdmin(ca);
  85. break;
  86. }
  87. case UpdateAdmin ua:
  88. {
  89. await HandleUpdateAdmin(ua);
  90. break;
  91. }
  92. case RemoveAdmin ra:
  93. {
  94. await HandleRemoveAdmin(ra);
  95. break;
  96. }
  97. case AddAdminRank ar:
  98. {
  99. await HandleAddAdminRank(ar);
  100. break;
  101. }
  102. case UpdateAdminRank ur:
  103. {
  104. await HandleUpdateAdminRank(ur);
  105. break;
  106. }
  107. case RemoveAdminRank ra:
  108. {
  109. await HandleRemoveAdminRank(ra);
  110. break;
  111. }
  112. }
  113. if (!IsShutDown)
  114. {
  115. LoadFromDb();
  116. }
  117. }
  118. private async Task HandleRemoveAdminRank(RemoveAdminRank rr)
  119. {
  120. var rank = await _db.GetAdminRankAsync(rr.Id);
  121. if (rank == null)
  122. {
  123. return;
  124. }
  125. if (!CanTouchRank(rank))
  126. {
  127. _sawmill.Warning($"{Player} tried to remove higher-ranked admin rank {rank.Name}");
  128. return;
  129. }
  130. await _db.RemoveAdminRankAsync(rr.Id);
  131. _adminManager.ReloadAdminsWithRank(rr.Id);
  132. }
  133. private async Task HandleUpdateAdminRank(UpdateAdminRank ur)
  134. {
  135. var rank = await _db.GetAdminRankAsync(ur.Id);
  136. if (rank == null)
  137. {
  138. return;
  139. }
  140. if (!CanTouchRank(rank))
  141. {
  142. _sawmill.Warning($"{Player} tried to update higher-ranked admin rank {rank.Name}");
  143. return;
  144. }
  145. if (!UserAdminFlagCheck(ur.Flags))
  146. {
  147. _sawmill.Warning($"{Player} tried to give a rank permissions above their authorization.");
  148. return;
  149. }
  150. rank.Flags = GenRankFlagList(ur.Flags);
  151. rank.Name = ur.Name;
  152. await _db.UpdateAdminRankAsync(rank);
  153. var flagText = string.Join(' ', AdminFlagsHelper.FlagsToNames(ur.Flags).Select(f => $"+{f}"));
  154. _sawmill.Info($"{Player} updated admin rank {rank.Name}/{flagText}.");
  155. _adminManager.ReloadAdminsWithRank(ur.Id);
  156. }
  157. private async Task HandleAddAdminRank(AddAdminRank ar)
  158. {
  159. if (!UserAdminFlagCheck(ar.Flags))
  160. {
  161. _sawmill.Warning($"{Player} tried to give a rank permissions above their authorization.");
  162. return;
  163. }
  164. var rank = new DbAdminRank
  165. {
  166. Name = ar.Name,
  167. Flags = GenRankFlagList(ar.Flags)
  168. };
  169. await _db.AddAdminRankAsync(rank);
  170. var flagText = string.Join(' ', AdminFlagsHelper.FlagsToNames(ar.Flags).Select(f => $"+{f}"));
  171. _sawmill.Info($"{Player} added admin rank {rank.Name}/{flagText}.");
  172. }
  173. private async Task HandleRemoveAdmin(RemoveAdmin ra)
  174. {
  175. var admin = await _db.GetAdminDataForAsync(ra.UserId);
  176. if (admin == null)
  177. {
  178. // Doesn't exist.
  179. return;
  180. }
  181. if (!CanTouchAdmin(admin))
  182. {
  183. _sawmill.Warning($"{Player} tried to remove higher-ranked admin {ra.UserId.ToString()}");
  184. return;
  185. }
  186. await _db.RemoveAdminAsync(ra.UserId);
  187. var record = await _db.GetPlayerRecordByUserId(ra.UserId);
  188. _sawmill.Info($"{Player} removed admin {record?.LastSeenUserName ?? ra.UserId.ToString()}");
  189. if (_playerManager.TryGetSessionById(ra.UserId, out var player))
  190. {
  191. _adminManager.ReloadAdmin(player);
  192. }
  193. }
  194. private async Task HandleUpdateAdmin(UpdateAdmin ua)
  195. {
  196. if (!CheckCreatePerms(ua.PosFlags, ua.NegFlags))
  197. {
  198. return;
  199. }
  200. var admin = await _db.GetAdminDataForAsync(ua.UserId);
  201. if (admin == null)
  202. {
  203. // Was removed in the mean time I guess?
  204. return;
  205. }
  206. if (!CanTouchAdmin(admin))
  207. {
  208. _sawmill.Warning($"{Player} tried to modify higher-ranked admin {ua.UserId.ToString()}");
  209. return;
  210. }
  211. admin.Title = ua.Title;
  212. admin.AdminRankId = ua.RankId;
  213. admin.Flags = GenAdminFlagList(ua.PosFlags, ua.NegFlags);
  214. admin.Suspended = ua.Suspended;
  215. await _db.UpdateAdminAsync(admin);
  216. var playerRecord = await _db.GetPlayerRecordByUserId(ua.UserId);
  217. var (bad, rankName) = await FetchAndCheckRank(ua.RankId);
  218. if (bad)
  219. {
  220. return;
  221. }
  222. var name = playerRecord?.LastSeenUserName ?? ua.UserId.ToString();
  223. var title = ua.Title ?? "<no title>";
  224. var flags = AdminFlagsHelper.PosNegFlagsText(ua.PosFlags, ua.NegFlags);
  225. _sawmill.Info($"{Player} updated admin {name} to {title}/{rankName}/{flags}");
  226. if (_playerManager.TryGetSessionById(ua.UserId, out var player))
  227. {
  228. _adminManager.ReloadAdmin(player);
  229. }
  230. }
  231. private async Task HandleCreateAdmin(AddAdmin ca)
  232. {
  233. if (!CheckCreatePerms(ca.PosFlags, ca.NegFlags))
  234. {
  235. return;
  236. }
  237. string name;
  238. NetUserId userId;
  239. if (Guid.TryParse(ca.UserNameOrId, out var guid))
  240. {
  241. userId = new NetUserId(guid);
  242. var playerRecord = await _db.GetPlayerRecordByUserId(userId);
  243. if (playerRecord == null)
  244. {
  245. name = userId.ToString();
  246. }
  247. else
  248. {
  249. name = playerRecord.LastSeenUserName;
  250. }
  251. }
  252. else
  253. {
  254. // Username entered, resolve user ID from DB.
  255. var dbPlayer = await _db.GetPlayerRecordByUserName(ca.UserNameOrId);
  256. if (dbPlayer == null)
  257. {
  258. // username not in DB.
  259. // TODO: Notify user.
  260. _sawmill.Warning($"{Player} tried to add admin with unknown username {ca.UserNameOrId}.");
  261. return;
  262. }
  263. userId = dbPlayer.UserId;
  264. name = ca.UserNameOrId;
  265. }
  266. var existing = await _db.GetAdminDataForAsync(userId);
  267. if (existing != null)
  268. {
  269. // Already exists.
  270. return;
  271. }
  272. var (bad, rankName) = await FetchAndCheckRank(ca.RankId);
  273. if (bad)
  274. {
  275. return;
  276. }
  277. rankName ??= "<no rank>";
  278. var admin = new Admin
  279. {
  280. Flags = GenAdminFlagList(ca.PosFlags, ca.NegFlags),
  281. AdminRankId = ca.RankId,
  282. UserId = userId.UserId,
  283. Title = ca.Title,
  284. Suspended = ca.Suspended,
  285. };
  286. await _db.AddAdminAsync(admin);
  287. var title = ca.Title ?? "<no title>";
  288. var flags = AdminFlagsHelper.PosNegFlagsText(ca.PosFlags, ca.NegFlags);
  289. _sawmill.Info($"{Player} added admin {name} as {title}/{rankName}/{flags}");
  290. if (_playerManager.TryGetSessionById(userId, out var player))
  291. {
  292. _adminManager.ReloadAdmin(player);
  293. }
  294. }
  295. // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
  296. private bool CheckCreatePerms(AdminFlags posFlags, AdminFlags negFlags)
  297. {
  298. if ((posFlags & negFlags) != 0)
  299. {
  300. // Can't have overlapping pos and neg flags.
  301. // Just deny the entire message.
  302. return false;
  303. }
  304. if (!UserAdminFlagCheck(posFlags))
  305. {
  306. // Can't create an admin with higher perms than yourself, obviously.
  307. _sawmill.Warning($"{Player} tried to grant admin powers above their authorization.");
  308. return false;
  309. }
  310. return true;
  311. }
  312. private async Task<(bool bad, string?)> FetchAndCheckRank(int? rankId)
  313. {
  314. string? ret = null;
  315. if (rankId is { } r)
  316. {
  317. var rank = await _db.GetAdminRankAsync(r);
  318. if (rank == null)
  319. {
  320. // Tried to set to nonexistent rank.
  321. _sawmill.Warning($"{Player} tried to assign nonexistent admin rank.");
  322. return (true, null);
  323. }
  324. ret = rank.Name;
  325. var rankFlags = AdminFlagsHelper.NamesToFlags(rank.Flags.Select(p => p.Flag));
  326. if (!UserAdminFlagCheck(rankFlags))
  327. {
  328. // Can't assign a rank with flags you don't have yourself.
  329. _sawmill.Warning($"{Player} tried to assign admin rank above their authorization.");
  330. return (true, null);
  331. }
  332. }
  333. return (false, ret);
  334. }
  335. private async void LoadFromDb()
  336. {
  337. StateDirty();
  338. _isLoading = true;
  339. var (admins, ranks) = await _db.GetAllAdminAndRanksAsync();
  340. _admins.Clear();
  341. _admins.AddRange(admins);
  342. _adminRanks.Clear();
  343. _adminRanks.AddRange(ranks);
  344. _isLoading = false;
  345. StateDirty();
  346. }
  347. private static List<AdminFlag> GenAdminFlagList(AdminFlags posFlags, AdminFlags negFlags)
  348. {
  349. var posFlagList = AdminFlagsHelper.FlagsToNames(posFlags);
  350. var negFlagList = AdminFlagsHelper.FlagsToNames(negFlags);
  351. return posFlagList
  352. .Select(f => new AdminFlag {Negative = false, Flag = f})
  353. .Concat(negFlagList.Select(f => new AdminFlag {Negative = true, Flag = f}))
  354. .ToList();
  355. }
  356. private static List<AdminRankFlag> GenRankFlagList(AdminFlags flags)
  357. {
  358. return AdminFlagsHelper.FlagsToNames(flags).Select(f => new AdminRankFlag {Flag = f}).ToList();
  359. }
  360. private bool UserAdminFlagCheck(AdminFlags flags)
  361. {
  362. return _adminManager.HasAdminFlag(Player, flags);
  363. }
  364. private bool CanTouchAdmin(Admin admin)
  365. {
  366. var posFlags = AdminFlagsHelper.NamesToFlags(admin.Flags.Where(f => !f.Negative).Select(f => f.Flag));
  367. var rankFlags = AdminFlagsHelper.NamesToFlags(
  368. admin.AdminRank?.Flags.Select(f => f.Flag) ?? Array.Empty<string>());
  369. var totalFlags = posFlags | rankFlags;
  370. return UserAdminFlagCheck(totalFlags);
  371. }
  372. private bool CanTouchRank(DbAdminRank rank)
  373. {
  374. var rankFlags = AdminFlagsHelper.NamesToFlags(rank.Flags.Select(f => f.Flag));
  375. return UserAdminFlagCheck(rankFlags);
  376. }
  377. }
  378. }