Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revision of airlock hacking protections #20265

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

35 changes: 35 additions & 0 deletions Content.Server/Construction/Completions/SetWiresPanelSecurity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Content.Shared.Construction;
using Content.Shared.Wires;
using JetBrains.Annotations;

namespace Content.Server.Construction.Completions;

/// <summary>
/// This graph action is used to set values on entities with the <see cref="WiresPanelSecurityComponent"/>
/// </summary>

[UsedImplicitly]
[DataDefinition]
public sealed partial class SetWiresPanelSecurity : IGraphAction
{
/// <summary>
/// Sets the Examine field on the entity's <see cref="WiresPanelSecurityComponent"/>
/// </summary>
[DataField("examine")]
public string Examine = string.Empty;

/// <summary>
/// Sets the WiresAccessible field on the entity's <see cref="WiresPanelSecurityComponent"/>
/// </summary>
[DataField("wiresAccessible")]
public bool WiresAccessible = true;

public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
{
if (entityManager.TryGetComponent(uid, out WiresPanelSecurityComponent? _))
{
var ev = new WiresPanelSecurityEvent(Examine, WiresAccessible);
entityManager.EventBus.RaiseLocalEvent(uid, ev);
}
}
}
43 changes: 43 additions & 0 deletions Content.Server/Construction/Conditions/HasTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Content.Shared.Construction;
using JetBrains.Annotations;
using Content.Shared.Doors.Components;
using Content.Shared.Examine;
using YamlDotNet.Core.Tokens;
using Content.Shared.Tag;

namespace Content.Server.Construction.Conditions
{
/// <summary>
/// This condition checks whether if an entity with the <see cref="TagComponent"/> possesses a specific tag
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed partial class HasTag : IGraphCondition
{
/// <summary>
/// The tag the entity is being checked for
/// </summary>
[DataField("tag")]
public string Tag { get; private set; }

public bool Condition(EntityUid uid, IEntityManager entityManager)
{
if (!entityManager.TrySystem<TagSystem>(out var tagSystem))
return false;

return tagSystem.HasTag(uid, Tag);
}

public bool DoExamine(ExaminedEvent args)
{
return false;
}

public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
{
yield return new ConstructionGuideEntry()
{
};
}
}
}
15 changes: 15 additions & 0 deletions Content.Server/Construction/ConstructionSystem.Graph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Robust.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using System.Linq;

namespace Content.Server.Construction
{
Expand Down Expand Up @@ -298,9 +299,23 @@ public bool ChangeNode(EntityUid uid, EntityUid? userUid, string id, bool perfor
throw new Exception("Missing construction components");
}

// Exit if the new entity's prototype is the same as the original, or the prototype is invalid
if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex<EntityPrototype>(newEntity))
return null;

// [Optional] Exit if the new entity's prototype is a parent of the original
// E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that
// had the 'Airlock' prototype, and DoNotReplaceInheritingEntities was true, the code block would
// exit here because 'AirlockCommand' is derived from 'Airlock'
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
metaData.EntityPrototype?.ID != null)
{
var parents = _prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();

if (parents != null && parents.Any(x => x.ID == newEntity))
return null;
}

// Optional resolves.
Resolve(uid, ref containerManager, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Content.Shared.Prying.Systems;
using Content.Shared.Radio.EntitySystems;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Utility;
Expand All @@ -38,7 +39,7 @@ private void InitializeInteractions()

// Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent.
SubscribeLocalEvent<ConstructionComponent, InteractUsingEvent>(EnqueueEvent,
new []{typeof(AnchorableSystem), typeof(PryingSystem) },
new []{typeof(AnchorableSystem), typeof(PryingSystem), typeof(WeldableSystem)},
new []{typeof(EncryptionKeySystem)});
SubscribeLocalEvent<ConstructionComponent, OnTemperatureChangeEvent>(EnqueueEvent);
SubscribeLocalEvent<ConstructionComponent, PartAssemblyPartInsertedEvent>(EnqueueEvent);
Expand Down
5 changes: 2 additions & 3 deletions Content.Server/Doors/Systems/AirlockSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public sealed class AirlockSystem : SharedAirlockSystem
[Dependency] private readonly WiresSystem _wiresSystem = default!;
[Dependency] private readonly PowerReceiverSystem _power = default!;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

public override void Initialize()
{
Expand Down Expand Up @@ -153,8 +152,8 @@ private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWor
{
if (TryComp<WiresPanelComponent>(uid, out var panel) &&
panel.Open &&
_prototypeManager.TryIndex<WiresPanelSecurityLevelPrototype>(panel.CurrentSecurityLevelID, out var securityLevelPrototype) &&
securityLevelPrototype.WiresAccessible &&
TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
wiresPanelSecurity.WiresAccessible &&
TryComp<ActorComponent>(args.User, out var actor))
{
_wiresSystem.OpenUserInterface(uid, actor.PlayerSession);
Expand Down
25 changes: 19 additions & 6 deletions Content.Server/Wires/WiresSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Linq;
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.Database;
Expand Down Expand Up @@ -33,6 +35,7 @@ public sealed class WiresSystem : SharedWiresSystem
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ConstructionSystem _construction = default!;

// This is where all the wire layouts are stored.
[ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
Expand All @@ -58,6 +61,7 @@ public override void Initialize()
SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, PanelChangedEvent>(OnActivatableUIPanelChanged);
SubscribeLocalEvent<WiresPanelSecurityComponent, WiresPanelSecurityEvent>(SetWiresPanelSecurity);
}
private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
{
Expand Down Expand Up @@ -459,8 +463,8 @@ private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUs
return;

if (panel.Open &&
_protoMan.TryIndex<WiresPanelSecurityLevelPrototype>(panel.CurrentSecurityLevelID, out var securityLevelPrototype) &&
securityLevelPrototype.WiresAccessible &&
TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
wiresPanelSecurity.WiresAccessible &&
(_toolSystem.HasQuality(args.Used, "Cutting", tool) ||
_toolSystem.HasQuality(args.Used, "Pulsing", tool)))
{
Expand Down Expand Up @@ -526,6 +530,14 @@ private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent arg
if (component.WireSeed == 0)
component.WireSeed = _random.Next(1, int.MaxValue);

// Update the construction graph to make sure that it starts on the node specified by WiresPanelSecurityComponent
if (TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
!string.IsNullOrEmpty(wiresPanelSecurity.SecurityLevel) &&
TryComp<ConstructionComponent>(uid, out var construction))
{
_construction.ChangeNode(uid, null, wiresPanelSecurity.SecurityLevel, true, construction);
}

UpdateUserInterface(uid);
}
#endregion
Expand Down Expand Up @@ -656,13 +668,14 @@ public void TogglePanel(EntityUid uid, WiresPanelComponent component, bool open)
RaiseLocalEvent(uid, ref ev);
}

public void SetWiresPanelSecurityData(EntityUid uid, WiresPanelComponent component, string wiresPanelSecurityLevelID)
public void SetWiresPanelSecurity(EntityUid uid, WiresPanelSecurityComponent component, WiresPanelSecurityEvent args)
{
component.CurrentSecurityLevelID = wiresPanelSecurityLevelID;
component.Examine = args.Examine;
component.WiresAccessible = args.WiresAccessible;

Dirty(uid, component);

if (_protoMan.TryIndex<WiresPanelSecurityLevelPrototype>(component.CurrentSecurityLevelID, out var securityLevelPrototype) &&
securityLevelPrototype.WiresAccessible)
if (!args.WiresAccessible)
{
_uiSystem.TryCloseAll(uid, WiresUiKey.Key);
}
Expand Down
2 changes: 1 addition & 1 deletion Content.Shared/Construction/ConstructionGraphEdge.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Content.Shared.Construction.Steps;
using Content.Shared.Construction.Steps;

namespace Content.Shared.Construction
{
Expand Down
16 changes: 15 additions & 1 deletion Content.Shared/Construction/ConstructionGraphNode.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Construction.NodeEntities;
using Content.Shared.Construction.Serialization;
using Robust.Shared.Prototypes;
Expand Down Expand Up @@ -31,6 +31,20 @@ public sealed partial class ConstructionGraphNode
[DataField("entity", customTypeSerializer: typeof(GraphNodeEntitySerializer), serverOnly:true)]
public IGraphNodeEntity Entity { get; private set; } = new NullNodeEntity();

/// <summary>
/// Ignore requests to change the entity if the entity's current prototype inherits from specified replacement
/// </summary>
/// <remarks>
/// When this bool is true and a construction node specifies that the current entity should be replaced with a new entity, if the
/// current entity has an entity prototype which inherits from the replacement entity prototype, entity replacement will not occur.
/// E.g., if an entity with the 'AirlockCommand' prototype was to be replaced with a new entity that had the 'Airlock' prototype,
/// and 'DoNotReplaceInheritingEntities' was true, the entity would not be replaced because 'AirlockCommand' is derived from 'Airlock'
/// This will largely be used for construction graphs which have removeable upgrades, such as hacking protections for airlocks,
/// so that the upgrades can be removed and you can return to the last primary construction step without replacing the entity
/// </remarks>
[DataField("doNotReplaceInheritingEntities")]
public bool DoNotReplaceInheritingEntities = false;

public ConstructionGraphEdge? GetEdge(string target)
{
foreach (var edge in _edges)
Expand Down
19 changes: 3 additions & 16 deletions Content.Shared/Wires/SharedWiresSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ namespace Content.Shared.Wires;

public abstract class SharedWiresSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<WiresPanelComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<WiresPanelComponent, WeldableAttemptEvent>(OnWeldableAttempt);
}

private void OnExamine(EntityUid uid, WiresPanelComponent component, ExaminedEvent args)
Expand All @@ -26,21 +23,11 @@ private void OnExamine(EntityUid uid, WiresPanelComponent component, ExaminedEve
{
args.PushMarkup(Loc.GetString("wires-panel-component-on-examine-open"));

if (_prototypeManager.TryIndex<WiresPanelSecurityLevelPrototype>(component.CurrentSecurityLevelID, out var securityLevelPrototype) &&
securityLevelPrototype.Examine != null)
if (TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
wiresPanelSecurity.Examine != null)
{
args.PushMarkup(Loc.GetString(securityLevelPrototype.Examine));
args.PushMarkup(Loc.GetString(wiresPanelSecurity.Examine));
}
}
}

private void OnWeldableAttempt(EntityUid uid, WiresPanelComponent component, WeldableAttemptEvent args)
{
if (component.Open &&
_prototypeManager.TryIndex<WiresPanelSecurityLevelPrototype>(component.CurrentSecurityLevelID, out var securityLevelPrototype) &&
!securityLevelPrototype.WeldingAllowed)
{
args.Cancel();
}
}
}
8 changes: 0 additions & 8 deletions Content.Shared/Wires/WiresPanelComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ public sealed partial class WiresPanelComponent : Component

[DataField("screwdriverCloseSound")]
public SoundSpecifier ScrewdriverCloseSound = new SoundPathSpecifier("/Audio/Machines/screwdriverclose.ogg");

/// <summary>
/// This prototype describes the current security features of the wire panel
/// </summary>
[DataField("securityLevel")]
[ValidatePrototypeId<WiresPanelSecurityLevelPrototype>]
[AutoNetworkedField]
public string CurrentSecurityLevelID = "Level0";
}

/// <summary>
Expand Down
50 changes: 50 additions & 0 deletions Content.Shared/Wires/WiresPanelSecurityComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Robust.Shared.GameStates;

namespace Content.Shared.Wires;

/// <summary>
/// Allows hacking protections to a be added to an entity.
/// These safeguards are determined via a construction graph,
/// so the entity requires <cref="ConstructionComponent"/> for this to function
/// </summary>
[NetworkedComponent, RegisterComponent]
[Access(typeof(SharedWiresSystem))]
[AutoGenerateComponentState]
public sealed partial class WiresPanelSecurityComponent : Component
{
/// <summary>
/// A verbal description of the wire panel's current security level
/// </summary>
[DataField("examine")]
[AutoNetworkedField]
public string? Examine = default!;

/// <summary>
/// Determines whether the wiring is accessible to hackers or not
/// </summary>
[DataField("wiresAccessible")]
[AutoNetworkedField]
public bool WiresAccessible = true;

/// <summary>
/// Name of the construction graph node that the entity will start on
/// </summary>
[DataField("securityLevel")]
[AutoNetworkedField]
public string SecurityLevel = string.Empty;
}

/// <summary>
/// This event gets raised when security settings on a wires panel change
/// </summary>
public sealed class WiresPanelSecurityEvent : EntityEventArgs
{
public readonly string? Examine;
public readonly bool WiresAccessible;

public WiresPanelSecurityEvent(string? examine, bool wiresAccessible)
{
Examine = examine;
WiresAccessible = wiresAccessible;
}
}
Loading