Add access configurator (#18638)

The access configurator programs the access levels of any access reader. To use the access configurator, players must:

- Insert an ID card
- Click a nearby entity with an access reader with the access configurator in hand
- Change the access list

Note that players only need one of the access levels listed on the device to lock/unlock it, but will only be able to alter access settings when they all of the access levels listed on the device

For example, an airlock which has 'Science' and 'Engineering' access listed can be opened by any player with either 'Science' or 'Engineering' access. However, to change the access settings on this airlock, a player must have both 'Science' and 'Engineering' access. This is to prevent people from easily breaking into secure areas with this tool, by adding one of their own access levels to the target device

Obviously, the most useful ID card to use with this tool is one with all access, since it can change the settings of any device. Removing all access requirements from a device will make it useable by anyone.

---------

Co-authored-by: Kevin Zheng <kevinz5000@gmail.com>
This commit is contained in:
chromiumboy
2023-08-08 13:30:46 -05:00
committed by GitHub
parent 4509312eea
commit 2df70799f8
16 changed files with 783 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
using Content.Shared.Access.Systems;
using JetBrains.Annotations;
namespace Content.Client.Access
{
[UsedImplicitly]
public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
{
}
}

View File

@@ -0,0 +1,72 @@
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.AccessOverriderComponent;
namespace Content.Client.Access.UI
{
public sealed class AccessOverriderBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SharedAccessOverriderSystem _accessOverriderSystem = default!;
private AccessOverriderWindow? _window;
public AccessOverriderBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_accessOverriderSystem = EntMan.System<SharedAccessOverriderSystem>();
}
protected override void Open()
{
base.Open();
List<string> accessLevels;
if (EntMan.TryGetComponent<AccessOverriderComponent>(Owner, out var accessOverrider))
{
accessLevels = accessOverrider.AccessLevels;
accessLevels.Sort();
}
else
{
accessLevels = new List<string>();
_accessOverriderSystem.Log.Error($"No AccessOverrider component found for {EntMan.ToPrettyString(Owner)}!");
}
_window = new AccessOverriderWindow(this, _prototypeManager, accessLevels)
{
Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName
};
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
_window.OnClose += Close;
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_window?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (AccessOverriderBoundUserInterfaceState) state;
_window?.UpdateState(castState);
}
public void SubmitData(List<string> newAccessList)
{
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
}
}
}

View File

@@ -0,0 +1,23 @@
<DefaultWindow xmlns="https://spacestation14.io"
MinSize="650 290">
<BoxContainer Orientation="Vertical">
<GridContainer Columns="2">
<GridContainer Columns="3" HorizontalExpand="True">
<Label Text="{Loc 'access-overrider-window-privileged-id'}" />
<Button Name="PrivilegedIdButton" Access="Public"/>
<Label Name="PrivilegedIdLabel" />
</GridContainer>
</GridContainer>
<Label Name="TargetNameLabel" />
<Control MinSize="0 8"/>
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
<!-- Access level buttons are added here by the C# code -->
</GridContainer>
<Control MinSize="0 8"/>
<Label Name="MissingPrivilegesLabel" />
<Control MinSize="0 4"/>
<Label Name="MissingPrivilegesText" />
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,111 @@
using System.Linq;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.AccessOverriderComponent;
namespace Content.Client.Access.UI
{
[GenerateTypedNameReferences]
public sealed partial class AccessOverriderWindow : DefaultWindow
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly ISawmill _logMill = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototypeManager prototypeManager,
List<string> accessLevels)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
_owner = owner;
foreach (var access in accessLevels)
{
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
{
_logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = GetAccessLevelName(accessLevel),
ToggleMode = true,
};
AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => SubmitData();
}
}
private static string GetAccessLevelName(AccessLevelPrototype prototype)
{
if (prototype.Name is { } name)
return Loc.GetString(name);
return prototype.ID;
}
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdLabel.Text = state.PrivilegedIdName;
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
? Loc.GetString("access-overrider-window-eject-button")
: Loc.GetString("access-overrider-window-insert-button");
TargetNameLabel.Text = state.TargetLabel;
TargetNameLabel.FontColorOverride = state.TargetLabelColor;
MissingPrivilegesLabel.Text = "";
MissingPrivilegesLabel.FontColorOverride = Color.Yellow;
MissingPrivilegesText.Text = "";
MissingPrivilegesText.FontColorOverride = Color.Yellow;
if (state.MissingPrivilegesList != null && state.MissingPrivilegesList.Any())
{
List<string> missingPrivileges = new List<string>();
foreach (string tag in state.MissingPrivilegesList)
{
string privilege = Loc.GetString(_prototypeManager.Index<AccessLevelPrototype>(tag)?.Name ?? "generic-unknown");
missingPrivileges.Add(privilege);
}
MissingPrivilegesLabel.Text = Loc.GetString("access-overrider-window-missing-privileges");
MissingPrivilegesText.Text = string.Join(", ", missingPrivileges);
}
var interfaceEnabled = state.IsPrivilegedIdPresent && state.IsPrivilegedIdAuthorized;
foreach (var (accessName, button) in _accessButtons)
{
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
button.Pressed = state.TargetAccessReaderIdAccessList?.Contains(accessName) ?? false;
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
}
}
}
private void SubmitData()
{
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
}