diff --git a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs index ae8da040ec..6b4df318b4 100644 --- a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs +++ b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs @@ -17,59 +17,60 @@ namespace Content.IntegrationTests.Tests.Access await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); var server = pairTracker.Pair.Server; + await server.WaitAssertion(() => { var system = EntitySystem.Get(); // test empty var reader = new AccessReaderComponent(); - Assert.That(system.IsAllowed(new[] { "Foo" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "Bar" }, reader), Is.True); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "Bar" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.True); // test deny reader = new AccessReaderComponent(); reader.DenyTags.Add("A"); - Assert.That(system.IsAllowed(new[] { "Foo" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "A", "Foo" }, reader), Is.False); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A", "Foo" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.True); // test one list reader = new AccessReaderComponent(); reader.AccessLists.Add(new HashSet { "A" }); - Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False); // test one list - two items reader = new AccessReaderComponent(); reader.AccessLists.Add(new HashSet { "A", "B" }); - Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False); // test two list reader = new AccessReaderComponent(); reader.AccessLists.Add(new HashSet { "A" }); reader.AccessLists.Add(new HashSet { "B", "C" }); - Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "C", "B" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "C", "B", "A" }, reader), Is.True); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B", "A" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False); // test deny list reader = new AccessReaderComponent(); reader.AccessLists.Add(new HashSet { "A" }); reader.DenyTags.Add("B"); - Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True); - Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False); - Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.False); - Assert.That(system.IsAllowed(new string[] { }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True); + Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.False); + Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False); }); await pairTracker.CleanReturnAsync(); } diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index 15578a0b98..5804e709d4 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -1,6 +1,6 @@ using System.Linq; using Content.Server.Station.Systems; -using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Administration.Logs; diff --git a/Content.Server/CrewManifest/CrewManifestSystem.cs b/Content.Server/CrewManifest/CrewManifestSystem.cs index 1bd61d872b..fe037edcc3 100644 --- a/Content.Server/CrewManifest/CrewManifestSystem.cs +++ b/Content.Server/CrewManifest/CrewManifestSystem.cs @@ -1,22 +1,17 @@ using System.Linq; using Content.Server.Administration; using Content.Server.EUI; -using Content.Server.GameTicking; using Content.Server.Station.Systems; using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.CrewManifest; using Content.Shared.GameTicking; -using Content.Shared.Roles; using Content.Shared.StationRecords; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Console; -using Robust.Shared.Player; -using Robust.Shared.Players; -using Robust.Shared.Prototypes; namespace Content.Server.CrewManifest; diff --git a/Content.Server/Mind/Commands/RenameCommand.cs b/Content.Server/Mind/Commands/RenameCommand.cs index 2986f578e1..7b3cb92c16 100644 --- a/Content.Server/Mind/Commands/RenameCommand.cs +++ b/Content.Server/Mind/Commands/RenameCommand.cs @@ -1,10 +1,9 @@ using Content.Server.Access.Systems; using Content.Server.Administration; using Content.Server.Administration.Systems; -using Content.Server.Cloning; using Content.Server.Mind.Components; using Content.Server.PDA; -using Content.Server.StationRecords; +using Content.Server.StationRecords.Systems; using Content.Shared.Access.Components; using Content.Shared.Administration; using Content.Shared.PDA; diff --git a/Content.Server/StationRecords/Components/StationRecordKeyStorageComponent.cs b/Content.Server/StationRecords/Components/StationRecordKeyStorageComponent.cs deleted file mode 100644 index a5e118c503..0000000000 --- a/Content.Server/StationRecords/Components/StationRecordKeyStorageComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Shared.StationRecords; - -namespace Content.Server.StationRecords; - -[RegisterComponent] -public sealed class StationRecordKeyStorageComponent : Component -{ - /// - /// The key stored in this component. - /// - [ViewVariables] - public StationRecordKey? Key; -} diff --git a/Content.Server/StationRecords/Components/StationRecordsComponent.cs b/Content.Server/StationRecords/Components/StationRecordsComponent.cs index 4cd295dc12..447d06385a 100644 --- a/Content.Server/StationRecords/Components/StationRecordsComponent.cs +++ b/Content.Server/StationRecords/Components/StationRecordsComponent.cs @@ -1,3 +1,5 @@ +using Content.Server.StationRecords.Systems; + namespace Content.Server.StationRecords; [Access(typeof(StationRecordsSystem))] diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs index e801cad73e..50f62d323b 100644 --- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs +++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs @@ -1,9 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using Content.Server.Access.Systems; +using System.Linq; using Content.Server.GameTicking; using Content.Server.Station.Systems; -using Content.Server.StationRecords; -using Content.Server.StationRecords.Systems; using Content.Shared.Access.Components; using Content.Shared.Inventory; using Content.Shared.PDA; @@ -13,6 +11,8 @@ using Content.Shared.StationRecords; using Robust.Shared.Enums; using Robust.Shared.Prototypes; +namespace Content.Server.StationRecords.Systems; + /// /// Station records. /// diff --git a/Content.Shared/Access/Components/AccessReaderComponent.cs b/Content.Shared/Access/Components/AccessReaderComponent.cs index 97fe78f7e6..a365ecae84 100644 --- a/Content.Shared/Access/Components/AccessReaderComponent.cs +++ b/Content.Shared/Access/Components/AccessReaderComponent.cs @@ -1,22 +1,52 @@ -namespace Content.Shared.Access.Components +using Content.Shared.Access.Systems; +using Content.Shared.StationRecords; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; + +namespace Content.Shared.Access.Components; + +/// +/// Stores access levels necessary to "use" an entity +/// and allows checking if something or somebody is authorized with these access levels. +/// +[RegisterComponent, NetworkedComponent] +public sealed class AccessReaderComponent : Component { /// - /// Stores access levels necessary to "use" an entity - /// and allows checking if something or somebody is authorized with these access levels. + /// The set of tags that will automatically deny an allowed check, if any of them are present. /// - [RegisterComponent] - public sealed class AccessReaderComponent : Component - { - /// - /// The set of tags that will automatically deny an allowed check, if any of them are present. - /// - public HashSet DenyTags = new(); + [DataField("denyTags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet DenyTags = new(); - /// - /// List of access lists to check allowed against. For an access check to pass - /// there has to be an access list that is a subset of the access in the checking list. - /// - [DataField("access")] - public List> AccessLists = new(); + /// + /// List of access lists to check allowed against. For an access check to pass + /// there has to be an access list that is a subset of the access in the checking list. + /// + [DataField("access")] + public List> AccessLists = new(); + + /// + /// A list of valid stationrecordkeys + /// + [DataField("accessKeys")] + public HashSet AccessKeys = new(); +} + +[Serializable, NetSerializable] +public sealed class AccessReaderComponentState : ComponentState +{ + public HashSet DenyTags; + + public List> AccessLists; + + public HashSet AccessKeys; + + public AccessReaderComponentState(HashSet denyTags, List> accessLists, HashSet accessKeys) + { + DenyTags = denyTags; + AccessLists = accessLists; + AccessKeys = accessKeys; } } + diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 4562d05858..b486d6dd9c 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -8,6 +8,8 @@ using Content.Shared.Access.Components; using Robust.Shared.Prototypes; using Content.Shared.Hands.EntitySystems; using Content.Shared.MachineLinking.Events; +using Content.Shared.StationRecords; +using Robust.Shared.GameStates; namespace Content.Shared.Access.Systems { @@ -23,6 +25,24 @@ namespace Content.Shared.Access.Systems SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnLinkAttempt); + + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + private void OnGetState(EntityUid uid, AccessReaderComponent component, ref ComponentGetState args) + { + args.State = new AccessReaderComponentState(component.DenyTags, component.AccessLists, + component.AccessKeys); + } + + private void OnHandleState(EntityUid uid, AccessReaderComponent component, ref ComponentHandleState args) + { + if (args.Current is not AccessReaderComponentState state) + return; + component.AccessKeys = new (state.AccessKeys); + component.AccessLists = new (state.AccessLists); + component.DenyTags = new (state.DenyTags); } private void OnLinkAttempt(EntityUid uid, AccessReaderComponent component, LinkAttemptEvent args) @@ -62,8 +82,7 @@ namespace Content.Shared.Access.Systems { if (!Resolve(target, ref reader, false)) return true; - var tags = FindAccessTags(source); - return IsAllowed(tags, reader); + return IsAllowed(source, reader); } /// @@ -74,8 +93,15 @@ namespace Content.Shared.Access.Systems /// A reader from a different entity public bool IsAllowed(EntityUid entity, AccessReaderComponent reader) { - var tags = FindAccessTags(entity); - return IsAllowed(tags, reader); + var allEnts = FindPotentialAccessItems(entity); + + if (AreAccessTagsAllowed(FindAccessTags(entity, allEnts), reader)) + return true; + + if (AreStationRecordKeysAllowed(FindStationRecordKeys(entity, allEnts), reader)) + return true; + + return false; } /// @@ -83,7 +109,7 @@ namespace Content.Shared.Access.Systems /// /// A list of access tags /// An access reader to check against - public bool IsAllowed(ICollection accessTags, AccessReaderComponent reader) + public bool AreAccessTagsAllowed(ICollection accessTags, AccessReaderComponent reader) { if (HasComp(reader.Owner)) { @@ -105,17 +131,18 @@ namespace Content.Shared.Access.Systems } /// - /// Finds the access tags on the given entity + /// Compares the given stationrecordkeys with the accessreader to see if it is allowed. /// - /// The entity that is being searched. - public ICollection FindAccessTags(EntityUid uid) + public bool AreStationRecordKeysAllowed(ICollection keys, AccessReaderComponent reader) { - HashSet? tags = null; - var owned = false; - - // check entity itself - FindAccessTagsItem(uid, ref tags, ref owned); + return keys.Any() && reader.AccessKeys.Any(keys.Contains); + } + /// + /// Finds all the items that could potentially give access to a given entity + /// + public HashSet FindPotentialAccessItems(EntityUid uid) + { FindAccessItemsInventory(uid, out var items); var ev = new GetAdditionalAccessEvent @@ -123,7 +150,23 @@ namespace Content.Shared.Access.Systems Entities = items }; RaiseLocalEvent(uid, ref ev); - foreach (var ent in ev.Entities) + items.Add(uid); + return items; + } + + /// + /// Finds the access tags on the given entity + /// + /// The entity that is being searched. + /// All of the items to search for access. If none are passed in, will be used. + public ICollection FindAccessTags(EntityUid uid, HashSet? items = null) + { + HashSet? tags = null; + var owned = false; + + items ??= FindPotentialAccessItems(uid); + + foreach (var ent in items) { FindAccessTagsItem(ent, ref tags, ref owned); } @@ -131,6 +174,26 @@ namespace Content.Shared.Access.Systems return (ICollection?) tags ?? Array.Empty(); } + /// + /// Finds the access tags on the given entity + /// + /// The entity that is being searched. + /// All of the items to search for access. If none are passed in, will be used. + public ICollection FindStationRecordKeys(EntityUid uid, HashSet? items = null) + { + HashSet keys = new(); + + items ??= FindPotentialAccessItems(uid); + + foreach (var ent in items) + { + if (FindStationRecordKeyItem(ent, out var key)) + keys.Add(key.Value); + } + + return keys; + } + /// /// Try to find on this item /// or inside this item (if it's pda) @@ -203,5 +266,31 @@ namespace Content.Shared.Access.Systems tags = null; return false; } + + /// + /// Try to find on this item + /// or inside this item (if it's pda) + /// + private bool FindStationRecordKeyItem(EntityUid uid, [NotNullWhen(true)] out StationRecordKey? key) + { + if (EntityManager.TryGetComponent(uid, out StationRecordKeyStorageComponent? storage) && storage.Key != null) + { + key = storage.Key; + return true; + } + + if (TryComp(uid, out var pda) && + pda.ContainedID?.Owner is {Valid: true} id) + { + if (TryComp(id, out var pdastorage) && pdastorage.Key != null) + { + key = pdastorage.Key; + return true; + } + } + + key = null; + return false; + } } } diff --git a/Content.Shared/Emag/Systems/EmagSystem.cs b/Content.Shared/Emag/Systems/EmagSystem.cs index d5db09e1e2..360cfa3141 100644 --- a/Content.Shared/Emag/Systems/EmagSystem.cs +++ b/Content.Shared/Emag/Systems/EmagSystem.cs @@ -130,7 +130,8 @@ namespace Content.Shared.Emag.Systems if (component.Charges <= 0) { - _popupSystem.PopupEntity(Loc.GetString("emag-no-charges"), user, user); + if (_net.IsServer) + _popupSystem.PopupEntity(Loc.GetString("emag-no-charges"), user, user); return false; } diff --git a/Content.Shared/StationRecords/StationRecordKeyStorageComponent.cs b/Content.Shared/StationRecords/StationRecordKeyStorageComponent.cs new file mode 100644 index 0000000000..87f789364a --- /dev/null +++ b/Content.Shared/StationRecords/StationRecordKeyStorageComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.StationRecords; + +[RegisterComponent, NetworkedComponent] +public sealed class StationRecordKeyStorageComponent : Component +{ + /// + /// The key stored in this component. + /// + [ViewVariables] + public StationRecordKey? Key; +} + +[Serializable, NetSerializable] +public sealed class StationRecordKeyStorageComponentState : ComponentState +{ + public StationRecordKey? Key; + + public StationRecordKeyStorageComponentState(StationRecordKey? key) + { + Key = key; + } +} diff --git a/Content.Server/StationRecords/Systems/StationRecordKeyStorageSystem.cs b/Content.Shared/StationRecords/StationRecordKeyStorageSystem.cs similarity index 63% rename from Content.Server/StationRecords/Systems/StationRecordKeyStorageSystem.cs rename to Content.Shared/StationRecords/StationRecordKeyStorageSystem.cs index d19444b644..94eda02c5f 100644 --- a/Content.Server/StationRecords/Systems/StationRecordKeyStorageSystem.cs +++ b/Content.Shared/StationRecords/StationRecordKeyStorageSystem.cs @@ -1,9 +1,29 @@ -using Content.Shared.StationRecords; +using Robust.Shared.GameStates; -namespace Content.Server.StationRecords.Systems; +namespace Content.Shared.StationRecords; public sealed class StationRecordKeyStorageSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + private void OnGetState(EntityUid uid, StationRecordKeyStorageComponent component, ref ComponentGetState args) + { + args.State = new StationRecordKeyStorageComponentState(component.Key); + } + + private void OnHandleState(EntityUid uid, StationRecordKeyStorageComponent component, ref ComponentHandleState args) + { + if (args.Current is not StationRecordKeyStorageComponentState state) + return; + component.Key = state.Key; + } + /// /// Assigns a station record key to an entity. /// @@ -18,6 +38,7 @@ public sealed class StationRecordKeyStorageSystem : EntitySystem } keyStorage.Key = key; + Dirty(keyStorage); } /// @@ -35,6 +56,7 @@ public sealed class StationRecordKeyStorageSystem : EntitySystem var key = keyStorage.Key; keyStorage.Key = null; + Dirty(keyStorage); return key; }