1
0

AccessOverriderSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. using System.Linq;
  2. using Content.Server.Popups;
  3. using Content.Shared.Access;
  4. using Content.Shared.Access.Components;
  5. using Content.Shared.Access.Systems;
  6. using Content.Shared.Administration.Logs;
  7. using Content.Shared.Database;
  8. using Content.Shared.DoAfter;
  9. using Content.Shared.Interaction;
  10. using JetBrains.Annotations;
  11. using Robust.Server.GameObjects;
  12. using Robust.Shared.Audio;
  13. using Robust.Shared.Audio.Systems;
  14. using Robust.Shared.Containers;
  15. using Robust.Shared.Player;
  16. using Robust.Shared.Prototypes;
  17. using static Content.Shared.Access.Components.AccessOverriderComponent;
  18. namespace Content.Server.Access.Systems;
  19. [UsedImplicitly]
  20. public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
  21. {
  22. [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
  23. [Dependency] private readonly AccessReaderSystem _accessReader = default!;
  24. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  25. [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
  26. [Dependency] private readonly PopupSystem _popupSystem = default!;
  27. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  28. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<AccessOverriderComponent, ComponentStartup>(UpdateUserInterface);
  33. SubscribeLocalEvent<AccessOverriderComponent, EntInsertedIntoContainerMessage>(UpdateUserInterface);
  34. SubscribeLocalEvent<AccessOverriderComponent, EntRemovedFromContainerMessage>(UpdateUserInterface);
  35. SubscribeLocalEvent<AccessOverriderComponent, AfterInteractEvent>(AfterInteractOn);
  36. SubscribeLocalEvent<AccessOverriderComponent, AccessOverriderDoAfterEvent>(OnDoAfter);
  37. Subs.BuiEvents<AccessOverriderComponent>(AccessOverriderUiKey.Key, subs =>
  38. {
  39. subs.Event<BoundUIOpenedEvent>(UpdateUserInterface);
  40. subs.Event<BoundUIClosedEvent>(OnClose);
  41. subs.Event<WriteToTargetAccessReaderIdMessage>(OnWriteToTargetAccessReaderIdMessage);
  42. });
  43. }
  44. private void AfterInteractOn(EntityUid uid, AccessOverriderComponent component, AfterInteractEvent args)
  45. {
  46. if (args.Target == null || !TryComp(args.Target, out AccessReaderComponent? accessReader))
  47. return;
  48. if (!_interactionSystem.InRangeUnobstructed(args.User, (EntityUid) args.Target))
  49. return;
  50. var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.DoAfter, new AccessOverriderDoAfterEvent(), uid, target: args.Target, used: uid)
  51. {
  52. BreakOnMove = true,
  53. BreakOnDamage = true,
  54. NeedHand = true,
  55. };
  56. _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
  57. }
  58. private void OnDoAfter(EntityUid uid, AccessOverriderComponent component, AccessOverriderDoAfterEvent args)
  59. {
  60. if (args.Handled || args.Cancelled)
  61. return;
  62. if (args.Args.Target != null)
  63. {
  64. component.TargetAccessReaderId = args.Args.Target.Value;
  65. _userInterface.OpenUi(uid, AccessOverriderUiKey.Key, args.User);
  66. UpdateUserInterface(uid, component, args);
  67. }
  68. args.Handled = true;
  69. }
  70. private void OnClose(EntityUid uid, AccessOverriderComponent component, BoundUIClosedEvent args)
  71. {
  72. if (args.UiKey.Equals(AccessOverriderUiKey.Key))
  73. {
  74. component.TargetAccessReaderId = new();
  75. }
  76. }
  77. private void OnWriteToTargetAccessReaderIdMessage(EntityUid uid, AccessOverriderComponent component, WriteToTargetAccessReaderIdMessage args)
  78. {
  79. if (args.Actor is not { Valid: true } player)
  80. return;
  81. TryWriteToTargetAccessReaderId(uid, args.AccessList, player, component);
  82. UpdateUserInterface(uid, component, args);
  83. }
  84. private void UpdateUserInterface(EntityUid uid, AccessOverriderComponent component, EntityEventArgs args)
  85. {
  86. if (!component.Initialized)
  87. return;
  88. var privilegedIdName = string.Empty;
  89. var targetLabel = Loc.GetString("access-overrider-window-no-target");
  90. var targetLabelColor = Color.Red;
  91. ProtoId<AccessLevelPrototype>[]? possibleAccess = null;
  92. ProtoId<AccessLevelPrototype>[]? currentAccess = null;
  93. ProtoId<AccessLevelPrototype>[]? missingAccess = null;
  94. if (component.TargetAccessReaderId is { Valid: true } accessReader)
  95. {
  96. targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
  97. targetLabelColor = Color.White;
  98. if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderEnt))
  99. return;
  100. var currentAccessHashsets = accessReaderEnt.Value.Comp.AccessLists;
  101. currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
  102. }
  103. if (component.PrivilegedIdSlot.Item is { Valid: true } idCard)
  104. {
  105. privilegedIdName = EntityManager.GetComponent<MetaDataComponent>(idCard).EntityName;
  106. if (component.TargetAccessReaderId is { Valid: true })
  107. {
  108. possibleAccess = _accessReader.FindAccessTags(idCard).ToArray();
  109. }
  110. if (currentAccess != null && possibleAccess != null)
  111. {
  112. missingAccess = currentAccess.Except(possibleAccess).ToArray();
  113. }
  114. }
  115. AccessOverriderBoundUserInterfaceState newState;
  116. newState = new AccessOverriderBoundUserInterfaceState(
  117. component.PrivilegedIdSlot.HasItem,
  118. PrivilegedIdIsAuthorized(uid, component),
  119. currentAccess,
  120. possibleAccess,
  121. missingAccess,
  122. privilegedIdName,
  123. targetLabel,
  124. targetLabelColor);
  125. _userInterface.SetUiState(uid, AccessOverriderUiKey.Key, newState);
  126. }
  127. private List<ProtoId<AccessLevelPrototype>> ConvertAccessHashSetsToList(List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets)
  128. {
  129. var accessList = new List<ProtoId<AccessLevelPrototype>>();
  130. if (accessHashsets.Count <= 0)
  131. return accessList;
  132. foreach (var hashSet in accessHashsets)
  133. {
  134. accessList.AddRange(hashSet);
  135. }
  136. return accessList;
  137. }
  138. private List<HashSet<ProtoId<AccessLevelPrototype>>> ConvertAccessListToHashSet(List<ProtoId<AccessLevelPrototype>> accessList)
  139. {
  140. List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets = new List<HashSet<ProtoId<AccessLevelPrototype>>>();
  141. if (accessList != null && accessList.Any())
  142. {
  143. foreach (ProtoId<AccessLevelPrototype> access in accessList)
  144. {
  145. accessHashsets.Add(new HashSet<ProtoId<AccessLevelPrototype>>() { access });
  146. }
  147. }
  148. return accessHashsets;
  149. }
  150. /// <summary>
  151. /// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader.
  152. /// </summary>
  153. private void TryWriteToTargetAccessReaderId(EntityUid uid,
  154. List<ProtoId<AccessLevelPrototype>> newAccessList,
  155. EntityUid player,
  156. AccessOverriderComponent? component = null)
  157. {
  158. if (!Resolve(uid, ref component) || component.TargetAccessReaderId is not { Valid: true })
  159. return;
  160. if (!PrivilegedIdIsAuthorized(uid, component))
  161. return;
  162. if (!_interactionSystem.InRangeUnobstructed(uid, component.TargetAccessReaderId))
  163. {
  164. _popupSystem.PopupEntity(Loc.GetString("access-overrider-out-of-range"), player, player);
  165. return;
  166. }
  167. if (newAccessList.Count > 0 && !newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
  168. {
  169. _sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag.");
  170. return;
  171. }
  172. if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReaderEnt))
  173. return;
  174. var oldTags = ConvertAccessHashSetsToList(accessReaderEnt.Value.Comp.AccessLists);
  175. var privilegedId = component.PrivilegedIdSlot.Item;
  176. if (oldTags.SequenceEqual(newAccessList))
  177. return;
  178. var difference = newAccessList.Union(oldTags).Except(newAccessList.Intersect(oldTags)).ToHashSet();
  179. var privilegedPerms = _accessReader.FindAccessTags(privilegedId!.Value).ToHashSet();
  180. if (!difference.IsSubsetOf(privilegedPerms))
  181. {
  182. _sawmill.Warning($"User {ToPrettyString(uid)} tried to modify permissions they could not give/take!");
  183. return;
  184. }
  185. if (!oldTags.ToHashSet().IsSubsetOf(privilegedPerms))
  186. {
  187. _sawmill.Warning($"User {ToPrettyString(uid)} tried to modify permissions when they do not have sufficient access!");
  188. _popupSystem.PopupEntity(Loc.GetString("access-overrider-cannot-modify-access"), player, player);
  189. _audioSystem.PlayPvs(component.DenialSound, uid);
  190. return;
  191. }
  192. var addedTags = newAccessList.Except(oldTags).Select(tag => "+" + tag).ToList();
  193. var removedTags = oldTags.Except(newAccessList).Select(tag => "-" + tag).ToList();
  194. _adminLogger.Add(LogType.Action, LogImpact.Medium,
  195. $"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
  196. accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
  197. var ev = new OnAccessOverriderAccessUpdatedEvent(player);
  198. RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
  199. Dirty(accessReaderEnt.Value);
  200. }
  201. /// <summary>
  202. /// Returns true if there is an ID in <see cref="AccessOverriderComponent.PrivilegedIdSlot"/> and said ID satisfies the requirements of <see cref="AccessReaderComponent"/>.
  203. /// </summary>
  204. /// <remarks>
  205. /// Other code relies on the fact this returns false if privileged Id is null. Don't break that invariant.
  206. /// </remarks>
  207. private bool PrivilegedIdIsAuthorized(EntityUid uid, AccessOverriderComponent? component = null)
  208. {
  209. if (!Resolve(uid, ref component))
  210. return true;
  211. if (_accessReader.GetMainAccessReader(uid, out var accessReader))
  212. return true;
  213. var privilegedId = component.PrivilegedIdSlot.Item;
  214. return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, accessReader);
  215. }
  216. }