From ad58a056d780585dd30e15228567697af615d733 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Fri, 30 Oct 2020 16:06:48 +0100 Subject: [PATCH] ConGroups are gone. Long live admin flags in content. --- .../Administration/ClientAdminManager.cs | 82 +++ .../Administration/IClientAdminManager.cs | 17 + Content.Client/ClientContentIoC.cs | 4 +- Content.Client/EntryPoint.cs | 2 + .../ContentIntegrationTest.cs | 2 - .../20201028210620_Admins.Designer.cs | 509 ++++++++++++++++++ .../Postgres/20201028210620_Admins.cs | 115 ++++ .../PostgresServerDbContextModelSnapshot.cs | 118 ++++ .../Sqlite/20201028210616_Admins.Designer.cs | 476 ++++++++++++++++ .../Sqlite/20201028210616_Admins.cs | 114 ++++ .../SqliteServerDbContextModelSnapshot.cs | 115 ++++ Content.Server.Database/Model.cs | 50 ++ .../Administration/AdminCommandAttribute.cs | 27 + Content.Server/Administration/AdminManager.cs | 377 +++++++++++++ Content.Server/Administration/AdminRank.cs | 18 + .../Administration/AnyCommandAttribute.cs | 18 + .../Administration/{ => Commands}/AGhost.cs | 4 +- .../{ => Commands}/BanCommand.cs | 5 +- .../{ => Commands}/ControlMob.cs | 4 +- .../Administration/Commands/DeAdminCommand.cs | 31 ++ .../DeleteEntitiesWithComponent.cs | 4 +- .../{ => Commands}/DeleteEntitiesWithId.cs | 4 +- .../Administration/Commands/ReAdminCommand.cs | 35 ++ .../Administration/{ => Commands}/ReadyAll.cs | 4 +- .../{ => Commands}/Rejuvenate.cs | 4 +- .../{ => Commands}/WarpCommand.cs | 4 +- .../Administration/IAdminManager.cs | 47 ++ Content.Server/Atmos/AtmosCommands.cs | 3 + Content.Server/Chat/ChatCommands.cs | 7 + Content.Server/Chat/ChatManager.cs | 12 +- .../Commands/AttachBodyPartCommand.cs | 3 + .../Commands/HideContainedContextCommand.cs | 3 + .../Commands/ShowContainedContextCommand.cs | 3 + Content.Server/Database/ServerDbBase.cs | 9 + Content.Server/Database/ServerDbManager.cs | 8 + Content.Server/EntryPoint.cs | 4 +- .../Components/Body/BodyCommands.cs | 6 + .../Components/Damage/DamageCommands.cs | 5 + .../Components/Disposal/DisposalCommands.cs | 3 + .../Components/Interactable/ToolCommands.cs | 8 +- .../Components/Mobs/Speech/SpeechComponent.cs | 9 +- .../Components/Observer/GhostComponent.cs | 4 +- .../EntitySystems/AI/AiFactionTagSystem.cs | 29 +- .../GameObjects/EntitySystems/AI/AiSystem.cs | 7 +- .../EntitySystems/SignalLinkerSystem.cs | 3 + Content.Server/GameTicking/GameTicker.cs | 1 + .../GameTicking/GameTickerCommands.cs | 14 + Content.Server/Mobs/Commands.cs | 7 + Content.Server/Observer/Ghost.cs | 2 + Content.Server/Players/PlayerData.cs | 6 + Content.Server/ServerContentIoC.cs | 4 +- Content.Server/ServerNotifyManager.cs | 3 + .../StationEvents/StationEventCommand.cs | 21 +- Content.Shared/Administration/AdminData.cs | 39 ++ Content.Shared/Administration/AdminFlags.cs | 68 +++ .../Administration/AdminFlagsExt.cs | 73 +++ Content.Shared/CCVars.cs | 3 + .../NetMessages/MsgUpdateAdminStatus.cs | 73 +++ .../Administration/AdminFlagsExtTest.cs | 35 ++ Resources/Groups/groups.yml | 233 -------- Resources/engineCommandPerms.yml | 63 +++ SpaceStation14.sln.DotSettings | 1 + 62 files changed, 2673 insertions(+), 289 deletions(-) create mode 100644 Content.Client/Administration/ClientAdminManager.cs create mode 100644 Content.Client/Administration/IClientAdminManager.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20201028210620_Admins.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20201028210620_Admins.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.cs create mode 100644 Content.Server/Administration/AdminCommandAttribute.cs create mode 100644 Content.Server/Administration/AdminManager.cs create mode 100644 Content.Server/Administration/AdminRank.cs create mode 100644 Content.Server/Administration/AnyCommandAttribute.cs rename Content.Server/Administration/{ => Commands}/AGhost.cs (93%) rename Content.Server/Administration/{ => Commands}/BanCommand.cs (93%) rename Content.Server/Administration/{ => Commands}/ControlMob.cs (94%) create mode 100644 Content.Server/Administration/Commands/DeAdminCommand.cs rename Content.Server/Administration/{ => Commands}/DeleteEntitiesWithComponent.cs (93%) rename Content.Server/Administration/{ => Commands}/DeleteEntitiesWithId.cs (90%) create mode 100644 Content.Server/Administration/Commands/ReAdminCommand.cs rename Content.Server/Administration/{ => Commands}/ReadyAll.cs (90%) rename Content.Server/Administration/{ => Commands}/Rejuvenate.cs (93%) rename Content.Server/Administration/{ => Commands}/WarpCommand.cs (97%) create mode 100644 Content.Server/Administration/IAdminManager.cs create mode 100644 Content.Shared/Administration/AdminData.cs create mode 100644 Content.Shared/Administration/AdminFlags.cs create mode 100644 Content.Shared/Administration/AdminFlagsExt.cs create mode 100644 Content.Shared/Network/NetMessages/MsgUpdateAdminStatus.cs create mode 100644 Content.Tests/Shared/Administration/AdminFlagsExtTest.cs delete mode 100644 Resources/Groups/groups.yml create mode 100644 Resources/engineCommandPerms.yml diff --git a/Content.Client/Administration/ClientAdminManager.cs b/Content.Client/Administration/ClientAdminManager.cs new file mode 100644 index 0000000000..eb5bfab65e --- /dev/null +++ b/Content.Client/Administration/ClientAdminManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Administration; +using Content.Shared.Network.NetMessages; +using Robust.Client.Console; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using Robust.Shared.Log; + +#nullable enable + +namespace Content.Client.Administration +{ + public class ClientAdminManager : IClientAdminManager, IClientConGroupImplementation, IPostInjectInit + { + [Dependency] private readonly IClientNetManager _netMgr = default!; + [Dependency] private readonly IClientConGroupController _conGroup = default!; + + private AdminData? _adminData; + private readonly HashSet _availableCommands = new HashSet(); + + public bool HasFlag(AdminFlags flag) + { + return _adminData?.HasFlag(flag) ?? false; + } + + public bool CanCommand(string cmdName) + { + return _availableCommands.Contains(cmdName); + } + + public bool CanViewVar() + { + return _adminData?.CanViewVar() ?? false; + } + + public bool CanAdminPlace() + { + return _adminData?.CanAdminPlace() ?? false; + } + + public bool CanScript() + { + return _adminData?.CanScript() ?? false; + } + + public bool CanAdminMenu() + { + return _adminData?.CanAdminMenu() ?? false; + } + + public void Initialize() + { + _netMgr.RegisterNetMessage(MsgUpdateAdminStatus.NAME, UpdateMessageRx); + } + + private void UpdateMessageRx(MsgUpdateAdminStatus message) + { + _availableCommands.Clear(); + _availableCommands.UnionWith(message.AvailableCommands); + Logger.DebugS("admin", $"Have {message.AvailableCommands.Length} commands available"); + + _adminData = message.Admin; + if (_adminData != null) + { + var flagsText = string.Join("|", AdminFlagsExt.FlagsToNames(_adminData.Flags)); + Logger.InfoS("admin", $"Updated admin status: {_adminData.Active}/{_adminData.Title}/{flagsText}"); + } + else + { + Logger.InfoS("admin", $"Updated admin status: Not admin"); + } + } + + public event Action? ConGroupUpdated; + + void IPostInjectInit.PostInject() + { + _conGroup.Implementation = this; + } + } +} diff --git a/Content.Client/Administration/IClientAdminManager.cs b/Content.Client/Administration/IClientAdminManager.cs new file mode 100644 index 0000000000..b6fca58ce2 --- /dev/null +++ b/Content.Client/Administration/IClientAdminManager.cs @@ -0,0 +1,17 @@ +using Content.Shared.Administration; + +namespace Content.Client.Administration +{ + public interface IClientAdminManager + { + bool HasFlag(AdminFlags flag); + + bool CanCommand(string cmdName); + bool CanViewVar(); + bool CanAdminPlace(); + bool CanScript(); + bool CanAdminMenu(); + + void Initialize(); + } +} diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index e7d8d90841..9e353cee87 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -1,4 +1,5 @@ -using Content.Client.Chat; +using Content.Client.Administration; +using Content.Client.Chat; using Content.Client.GameTicking; using Content.Client.Interfaces; using Content.Client.Interfaces.Chat; @@ -35,6 +36,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 4eab611fc2..8512a2231a 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -1,4 +1,5 @@ using System; +using Content.Client.Administration; using Content.Client.GameObjects.Components.Actor; using Content.Client.Input; using Content.Client.Interfaces; @@ -88,6 +89,7 @@ namespace Content.Client IoCManager.BuildGraph(); + IoCManager.Resolve().Initialize(); IoCManager.Resolve().LoadParallax(); IoCManager.Resolve().PlayerJoinedServer += SubscribePlayerAttachmentEvents; IoCManager.Resolve().Initialize(); diff --git a/Content.IntegrationTests/ContentIntegrationTest.cs b/Content.IntegrationTests/ContentIntegrationTest.cs index 85184d6f65..e73a3247ca 100644 --- a/Content.IntegrationTests/ContentIntegrationTest.cs +++ b/Content.IntegrationTests/ContentIntegrationTest.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Threading.Tasks; using Content.Client; using Content.Client.Interfaces.Parallax; @@ -13,7 +12,6 @@ using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using Robust.UnitTesting; using EntryPoint = Content.Client.EntryPoint; diff --git a/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.Designer.cs b/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.Designer.cs new file mode 100644 index 0000000000..ae0daea2ad --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.Designer.cs @@ -0,0 +1,509 @@ +// +using System; +using System.Net; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20201028210620_Admins")] + partial class Admins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnName("title") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_flag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdminId") + .HasColumnName("admin_id") + .HasColumnType("uuid"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("text"); + + b.Property("Negative") + .HasColumnName("negative") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.ToTable("admin_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("admin_rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_flag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("integer"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin_rank_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("text"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("text"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("integer"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("inet"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player"); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("server_ban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property?>("Address") + .HasColumnName("address") + .HasColumnType("inet"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("uuid"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("timestamp with time zone"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + b.HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("integer"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("timestamp with time zone"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("integer"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("text"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("text"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("text"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("text"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("text"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("text"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("integer"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("integer"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("text"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("text"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.HasOne("Content.Server.Database.PostgresServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.cs b/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.cs new file mode 100644 index 0000000000..02514652da --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20201028210620_Admins.cs @@ -0,0 +1,115 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class Admins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "admin_rank", + columns: table => new + { + admin_rank_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_rank", x => x.admin_rank_id); + }); + + migrationBuilder.CreateTable( + name: "admin", + columns: table => new + { + user_id = table.Column(nullable: false), + title = table.Column(nullable: true), + admin_rank_id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_admin", x => x.user_id); + table.ForeignKey( + name: "FK_admin_admin_rank_admin_rank_id", + column: x => x.admin_rank_id, + principalTable: "admin_rank", + principalColumn: "admin_rank_id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "admin_rank_flag", + columns: table => new + { + admin_rank_flag_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + flag = table.Column(nullable: false), + admin_rank_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_rank_flag", x => x.admin_rank_flag_id); + table.ForeignKey( + name: "FK_admin_rank_flag_admin_rank_admin_rank_id", + column: x => x.admin_rank_id, + principalTable: "admin_rank", + principalColumn: "admin_rank_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "admin_flag", + columns: table => new + { + admin_flag_id = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + flag = table.Column(nullable: false), + negative = table.Column(nullable: false), + admin_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_flag", x => x.admin_flag_id); + table.ForeignKey( + name: "FK_admin_flag_admin_admin_id", + column: x => x.admin_id, + principalTable: "admin", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_admin_admin_rank_id", + table: "admin", + column: "admin_rank_id"); + + migrationBuilder.CreateIndex( + name: "IX_admin_flag_admin_id", + table: "admin_flag", + column: "admin_id"); + + migrationBuilder.CreateIndex( + name: "IX_admin_rank_flag_admin_rank_id", + table: "admin_rank_flag", + column: "admin_rank_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "admin_flag"); + + migrationBuilder.DropTable( + name: "admin_rank_flag"); + + migrationBuilder.DropTable( + name: "admin"); + + migrationBuilder.DropTable( + name: "admin_rank"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 59ae3c45f4..916c5843f2 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -20,6 +20,98 @@ namespace Content.Server.Database.Migrations.Postgres .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnName("user_id") + .HasColumnType("uuid"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnName("title") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_flag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdminId") + .HasColumnName("admin_id") + .HasColumnType("uuid"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("text"); + + b.Property("Negative") + .HasColumnName("negative") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.ToTable("admin_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("admin_rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_flag_id") + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("integer"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin_rank_flag"); + }); + modelBuilder.Entity("Content.Server.Database.Antag", b => { b.Property("Id") @@ -348,6 +440,32 @@ namespace Content.Server.Database.Migrations.Postgres b.ToTable("profile"); }); + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Content.Server.Database.Antag", b => { b.HasOne("Content.Server.Database.Profile", "Profile") diff --git a/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.Designer.cs new file mode 100644 index 0000000000..23ebe0d41b --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.Designer.cs @@ -0,0 +1,476 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20201028210616_Admins")] + partial class Admins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnName("title") + .HasColumnType("TEXT"); + + b.HasKey("UserId"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_flag_id") + .HasColumnType("INTEGER"); + + b.Property("AdminId") + .HasColumnName("admin_id") + .HasColumnType("TEXT"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("TEXT"); + + b.Property("Negative") + .HasColumnName("negative") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.ToTable("admin_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("admin_rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_flag_id") + .HasColumnType("INTEGER"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin_rank_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("antag_id") + .HasColumnType("INTEGER"); + + b.Property("AntagName") + .IsRequired() + .HasColumnName("antag_name") + .HasColumnType("TEXT"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("assigned_user_id_id") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("job_id") + .HasColumnType("INTEGER"); + + b.Property("JobName") + .IsRequired() + .HasColumnName("job_name") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnName("priority") + .HasColumnType("INTEGER"); + + b.Property("ProfileId") + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnName("selected_character_slot") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("profile_id") + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnName("age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnName("char_name") + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnName("eye_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnName("facial_hair_color") + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnName("facial_hair_name") + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnName("hair_color") + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnName("hair_name") + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnName("preference_id") + .HasColumnType("INTEGER"); + + b.Property("PreferenceUnavailable") + .HasColumnName("pref_unavailable") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnName("sex") + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnName("skin_color") + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnName("slot") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("connection_log_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .IsRequired() + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnName("time") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("user_name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("connection_log"); + }); + + modelBuilder.Entity("Content.Server.Database.SqlitePlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("player_id") + .HasColumnType("INTEGER"); + + b.Property("FirstSeenTime") + .HasColumnName("first_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnName("last_seen_address") + .HasColumnType("TEXT"); + + b.Property("LastSeenTime") + .HasColumnName("last_seen_time") + .HasColumnType("TEXT"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnName("last_seen_user_name") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("player"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("Address") + .HasColumnName("address") + .HasColumnType("TEXT"); + + b.Property("BanTime") + .HasColumnName("ban_time") + .HasColumnType("TEXT"); + + b.Property("BanningAdmin") + .HasColumnName("banning_admin") + .HasColumnType("TEXT"); + + b.Property("ExpirationTime") + .HasColumnName("expiration_time") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasColumnName("reason") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ban"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("unban_id") + .HasColumnType("INTEGER"); + + b.Property("BanId") + .HasColumnName("ban_id") + .HasColumnType("INTEGER"); + + b.Property("UnbanTime") + .HasColumnName("unban_time") + .HasColumnType("TEXT"); + + b.Property("UnbanningAdmin") + .HasColumnName("unbanning_admin") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.HasOne("Content.Server.Database.SqliteServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.cs b/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.cs new file mode 100644 index 0000000000..c9c65034d4 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20201028210616_Admins.cs @@ -0,0 +1,114 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class Admins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "admin_rank", + columns: table => new + { + admin_rank_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + name = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_rank", x => x.admin_rank_id); + }); + + migrationBuilder.CreateTable( + name: "admin", + columns: table => new + { + user_id = table.Column(nullable: false), + title = table.Column(nullable: true), + admin_rank_id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_admin", x => x.user_id); + table.ForeignKey( + name: "FK_admin_admin_rank_admin_rank_id", + column: x => x.admin_rank_id, + principalTable: "admin_rank", + principalColumn: "admin_rank_id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "admin_rank_flag", + columns: table => new + { + admin_rank_flag_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + flag = table.Column(nullable: false), + admin_rank_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_rank_flag", x => x.admin_rank_flag_id); + table.ForeignKey( + name: "FK_admin_rank_flag_admin_rank_admin_rank_id", + column: x => x.admin_rank_id, + principalTable: "admin_rank", + principalColumn: "admin_rank_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "admin_flag", + columns: table => new + { + admin_flag_id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + flag = table.Column(nullable: false), + negative = table.Column(nullable: false), + admin_id = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_admin_flag", x => x.admin_flag_id); + table.ForeignKey( + name: "FK_admin_flag_admin_admin_id", + column: x => x.admin_id, + principalTable: "admin", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_admin_admin_rank_id", + table: "admin", + column: "admin_rank_id"); + + migrationBuilder.CreateIndex( + name: "IX_admin_flag_admin_id", + table: "admin_flag", + column: "admin_id"); + + migrationBuilder.CreateIndex( + name: "IX_admin_rank_flag_admin_rank_id", + table: "admin_rank_flag", + column: "admin_rank_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "admin_flag"); + + migrationBuilder.DropTable( + name: "admin_rank_flag"); + + migrationBuilder.DropTable( + name: "admin"); + + migrationBuilder.DropTable( + name: "admin_rank"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index b00a7e32c7..67f92bea49 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -16,6 +16,95 @@ namespace Content.Server.Database.Migrations.Sqlite modelBuilder .HasAnnotation("ProductVersion", "3.1.4"); + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnName("user_id") + .HasColumnType("TEXT"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnName("title") + .HasColumnType("TEXT"); + + b.HasKey("UserId"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_flag_id") + .HasColumnType("INTEGER"); + + b.Property("AdminId") + .HasColumnName("admin_id") + .HasColumnType("TEXT"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("TEXT"); + + b.Property("Negative") + .HasColumnName("negative") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AdminId"); + + b.ToTable("admin_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnName("name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("admin_rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnName("admin_rank_flag_id") + .HasColumnType("INTEGER"); + + b.Property("AdminRankId") + .HasColumnName("admin_rank_id") + .HasColumnType("INTEGER"); + + b.Property("Flag") + .IsRequired() + .HasColumnName("flag") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin_rank_flag"); + }); + modelBuilder.Entity("Content.Server.Database.Antag", b => { b.Property("Id") @@ -318,6 +407,32 @@ namespace Content.Server.Database.Migrations.Sqlite b.ToTable("unban"); }); + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Content.Server.Database.Antag", b => { b.HasOne("Content.Server.Database.Profile", "Profile") diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 5aa4f6ec81..d701c5cfcc 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; @@ -26,6 +27,8 @@ namespace Content.Server.Database public DbSet Preference { get; set; } = null!; public DbSet Profile { get; set; } = null!; public DbSet AssignedUserId { get; set; } = null!; + public DbSet Admin { get; set; } = null!; + public DbSet AdminRank { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -49,6 +52,11 @@ namespace Content.Server.Database modelBuilder.Entity() .HasIndex(p => p.UserId) .IsUnique(); + + modelBuilder.Entity() + .HasOne(p => p.AdminRank) + .WithMany(p => p!.Admins) + .OnDelete(DeleteBehavior.SetNull); } } @@ -135,4 +143,46 @@ namespace Content.Server.Database [Column("user_id")] public Guid UserId { get; set; } } + + [Table("admin")] + public class Admin + { + [Column("user_id"), Key] public Guid UserId { get; set; } + [Column("title")] public string? Title { get; set; } + + [Column("admin_rank_id")] public int? AdminRankId { get; set; } + public AdminRank? AdminRank { get; set; } + public List Flags { get; set; } = default!; + } + + [Table("admin_flag")] + public class AdminFlag + { + [Column("admin_flag_id")] public int Id { get; set; } + [Column("flag")] public string Flag { get; set; } = default!; + [Column("negative")] public bool Negative { get; set; } + + [Column("admin_id")] public Guid AdminId { get; set; } + public Admin Admin { get; set; } = default!; + } + + [Table("admin_rank")] + public class AdminRank + { + [Column("admin_rank_id")] public int Id { get; set; } + [Column("name")] public string Name { get; set; } = default!; + + public List Admins { get; set; } = default!; + public List Flags { get; set; } = default!; + } + + [Table("admin_rank_flag")] + public class AdminRankFlag + { + [Column("admin_rank_flag_id")] public int Id { get; set; } + [Column("flag")] public string Flag { get; set; } = default!; + + [Column("admin_rank_id")] public int AdminRankId { get; set; } + public AdminRank Rank { get; set; } = default!; + } } diff --git a/Content.Server/Administration/AdminCommandAttribute.cs b/Content.Server/Administration/AdminCommandAttribute.cs new file mode 100644 index 0000000000..79ae7fc769 --- /dev/null +++ b/Content.Server/Administration/AdminCommandAttribute.cs @@ -0,0 +1,27 @@ +using System; +using Content.Shared.Administration; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Console; + +namespace Content.Server.Administration +{ + /// + /// Specifies that a command can only be executed by an admin with the specified flags. + /// + /// + /// If this attribute is used multiple times, either attribute's flag sets can be used to get access. + /// + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(IClientCommand))] + [MeansImplicitUse] + public sealed class AdminCommandAttribute : Attribute + { + public AdminCommandAttribute(AdminFlags flags) + { + Flags = flags; + } + + public AdminFlags Flags { get; } + } +} diff --git a/Content.Server/Administration/AdminManager.cs b/Content.Server/Administration/AdminManager.cs new file mode 100644 index 0000000000..1b4862bfe6 --- /dev/null +++ b/Content.Server/Administration/AdminManager.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using Content.Server.Database; +using Content.Server.Players; +using Content.Shared; +using Content.Shared.Administration; +using Content.Shared.Network.NetMessages; +using Robust.Server.Console; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Resources; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +#nullable enable + +namespace Content.Server.Administration +{ + public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation + { + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerDbManager _dbManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IServerNetManager _netMgr = default!; + [Dependency] private readonly IConGroupController _conGroup = default!; + [Dependency] private readonly IResourceManager _res = default!; + [Dependency] private readonly IConsoleShell _consoleShell = default!; + + private readonly Dictionary _admins = new Dictionary(); + + public IEnumerable ActiveAdmins => _admins + .Where(p => p.Value.Data.Active) + .Select(p => p.Key); + + // If a command isn't in this list it's server-console only. + // if a command is in but the flags value is null it's available to everybody. + private readonly HashSet _anyCommands = new HashSet(); + private readonly Dictionary _adminCommands = new Dictionary(); + + public AdminData? GetAdminData(IPlayerSession session, bool includeDeAdmin = false) + { + if (_admins.TryGetValue(session, out var reg) && (reg.Data.Active || includeDeAdmin)) + { + return reg.Data; + } + + return null; + } + + public void DeAdmin(IPlayerSession session) + { + if (!_admins.TryGetValue(session, out var reg)) + { + throw new ArgumentException($"Player {session} is not an admin"); + } + + if (!reg.Data.Active) + { + return; + } + + var plyData = session.ContentData()!; + plyData.ExplicitlyDeadminned = true; + reg.Data.Active = false; + + // TODO: Send messages to all admins. + + UpdateAdminStatus(session); + } + + public void ReAdmin(IPlayerSession session) + { + if (!_admins.TryGetValue(session, out var reg)) + { + throw new ArgumentException($"Player {session} is not an admin"); + } + + var plyData = session.ContentData()!; + plyData.ExplicitlyDeadminned = true; + reg.Data.Active = true; + + // TODO: Send messages to all admins. + + UpdateAdminStatus(session); + } + + public void Initialize() + { + _netMgr.RegisterNetMessage(MsgUpdateAdminStatus.NAME); + + // Cache permissions for loaded console commands with the requisite attributes. + foreach (var (cmdName, cmd) in _consoleShell.AvailableCommands) + { + var (isAvail, flagsReq) = GetRequiredFlag(cmd); + + if (!isAvail) + { + continue; + } + + if (flagsReq.Length != 0) + { + _adminCommands.Add(cmdName, flagsReq); + } + else + { + _anyCommands.Add(cmdName); + } + } + + // Load flags for engine commands, since those don't have the attributes. + if (_res.TryContentFileRead(new ResourcePath("/engineCommandPerms.yml"), out var fs)) + { + using var reader = new StreamReader(fs, EncodingHelpers.UTF8); + var yStream = new YamlStream(); + yStream.Load(reader); + var root = (YamlSequenceNode) yStream.Documents[0].RootNode; + + foreach (var child in root) + { + var map = (YamlMappingNode) child; + var commands = map.GetNode("Commands").Select(p => p.AsString()); + if (map.TryGetNode("Flags", out var flagsNode)) + { + var flagNames = flagsNode.AsString().Split(",", StringSplitOptions.RemoveEmptyEntries); + var flags = AdminFlagsExt.NamesToFlags(flagNames); + foreach (var cmd in commands) + { + if (!_adminCommands.TryGetValue(cmd, out var exFlags)) + { + _adminCommands.Add(cmd, new []{flags}); + } + else + { + var newArr = new AdminFlags[exFlags.Length + 1]; + exFlags.CopyTo(newArr, 0); + exFlags[^1] = flags; + _adminCommands[cmd] = newArr; + } + } + } + else + { + _anyCommands.UnionWith(commands); + } + } + } + } + + void IPostInjectInit.PostInject() + { + _playerManager.PlayerStatusChanged += PlayerStatusChanged; + _conGroup.Implementation = this; + } + + // NOTE: Also sends commands list for non admins.. + private void UpdateAdminStatus(IPlayerSession session) + { + var msg = _netMgr.CreateNetMessage(); + + var commands = new List(_anyCommands); + + if (_admins.TryGetValue(session, out var adminData)) + { + msg.Admin = adminData.Data; + + commands.AddRange(_adminCommands + .Where(p => p.Value.Any(f => adminData.Data.HasFlag(f))) + .Select(p => p.Key)); + } + + msg.AvailableCommands = commands.ToArray(); + + _netMgr.ServerSendMessage(msg, session.ConnectedClient); + } + + private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus == SessionStatus.Connected) + { + // Run this so that available commands list gets sent. + UpdateAdminStatus(e.Session); + } + else if (e.NewStatus == SessionStatus.InGame) + { + LoginAdminMaybe(e.Session); + } + else if (e.NewStatus == SessionStatus.Disconnected) + { + _admins.Remove(e.Session); + } + } + + private async void LoginAdminMaybe(IPlayerSession session) + { + AdminReg reg; + if (IsLocal(session) && _cfg.GetCVar(CCVars.ConsoleLoginLocal)) + { + var data = new AdminData + { + Title = Loc.GetString("Host"), + Flags = AdminFlagsExt.Everything, + }; + + reg = new AdminReg(session, data) + { + IsSpecialLogin = true, + }; + } + else + { + var dbData = await _dbManager.GetAdminDataForAsync(session.UserId); + + if (dbData == null) + { + // Not an admin! + return; + } + + var flags = AdminFlags.None; + + if (dbData.AdminRank != null) + { + flags = AdminFlagsExt.NamesToFlags(dbData.AdminRank.Flags.Select(p => p.Flag)); + } + + foreach (var dbFlag in dbData.Flags) + { + var flag = AdminFlagsExt.NameToFlag(dbFlag.Flag); + if (dbFlag.Negative) + { + flags &= ~flag; + } + else + { + flags |= flag; + } + } + + var data = new AdminData + { + Flags = flags + }; + + if (dbData.Title != null) + { + data.Title = dbData.Title; + } + else if (dbData.AdminRank != null) + { + data.Title = dbData.AdminRank.Name; + } + + reg = new AdminReg(session, data); + } + + _admins.Add(session, reg); + + if (!session.ContentData()!.ExplicitlyDeadminned) + { + reg.Data.Active = true; + } + + UpdateAdminStatus(session); + } + + private static bool IsLocal(IPlayerSession player) + { + var ep = player.ConnectedClient.RemoteEndPoint; + var addr = ep.Address; + if (addr.IsIPv4MappedToIPv6) + { + addr = addr.MapToIPv4(); + } + + return Equals(addr, IPAddress.Loopback) || Equals(addr, IPAddress.IPv6Loopback); + } + + public bool CanCommand(IPlayerSession session, string cmdName) + { + if (_anyCommands.Contains(cmdName)) + { + // Anybody can use this command. + return true; + } + + if (!_adminCommands.TryGetValue(cmdName, out var flagsReq)) + { + // Server-console only. + return false; + } + + var data = GetAdminData(session); + if (data == null) + { + // Player isn't an admin. + return false; + } + + foreach (var flagReq in flagsReq) + { + if (data.HasFlag(flagReq)) + { + return true; + } + } + + return false; + } + + private static (bool isAvail, AdminFlags[] flagsReq) GetRequiredFlag(IClientCommand cmd) + { + var type = cmd.GetType(); + if (Attribute.IsDefined(type, typeof(AnyCommandAttribute))) + { + // Available to everybody. + return (true, Array.Empty()); + } + + var attribs = type.GetCustomAttributes(typeof(AdminCommandAttribute)) + .Cast() + .Select(p => p.Flags) + .ToArray(); + + // If attribs.length == 0 then no access attribute is specified, + // and this is a server-only command. + return (attribs.Length != 0, attribs); + } + + public bool CanViewVar(IPlayerSession session) + { + return GetAdminData(session)?.CanViewVar() ?? false; + } + + public bool CanAdminPlace(IPlayerSession session) + { + return GetAdminData(session)?.CanAdminPlace() ?? false; + } + + public bool CanScript(IPlayerSession session) + { + return GetAdminData(session)?.CanScript() ?? false; + } + + public bool CanAdminMenu(IPlayerSession session) + { + return GetAdminData(session)?.CanAdminMenu() ?? false; + } + + private sealed class AdminReg + { + public IPlayerSession Session; + + public AdminData Data; + + // Such as console.loginlocal + // Means that stuff like permissions editing is blocked. + public bool IsSpecialLogin; + + public AdminReg(IPlayerSession session, AdminData data) + { + Data = data; + Session = session; + } + } + } +} diff --git a/Content.Server/Administration/AdminRank.cs b/Content.Server/Administration/AdminRank.cs new file mode 100644 index 0000000000..925022498e --- /dev/null +++ b/Content.Server/Administration/AdminRank.cs @@ -0,0 +1,18 @@ +using Content.Shared.Administration; + +#nullable enable + +namespace Content.Server.Administration +{ + public sealed class AdminRank + { + public AdminRank(string name, AdminFlags flags) + { + Name = name; + Flags = flags; + } + + public string Name { get; } + public AdminFlags Flags { get; } + } +} diff --git a/Content.Server/Administration/AnyCommandAttribute.cs b/Content.Server/Administration/AnyCommandAttribute.cs new file mode 100644 index 0000000000..ab9895f728 --- /dev/null +++ b/Content.Server/Administration/AnyCommandAttribute.cs @@ -0,0 +1,18 @@ +using System; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Console; + +namespace Content.Server.Administration +{ + /// + /// Specifies that a command can be executed by any player. + /// + /// + [AttributeUsage(AttributeTargets.Class)] + [BaseTypeRequired(typeof(IClientCommand))] + [MeansImplicitUse] + public sealed class AnyCommandAttribute : Attribute + { + + } +} diff --git a/Content.Server/Administration/AGhost.cs b/Content.Server/Administration/Commands/AGhost.cs similarity index 93% rename from Content.Server/Administration/AGhost.cs rename to Content.Server/Administration/Commands/AGhost.cs index 5226c4637f..89c71d0346 100644 --- a/Content.Server/Administration/AGhost.cs +++ b/Content.Server/Administration/Commands/AGhost.cs @@ -1,12 +1,14 @@ using Content.Server.GameObjects.Components.Observer; using Content.Server.Players; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] public class AGhost : IClientCommand { public string Command => "aghost"; diff --git a/Content.Server/Administration/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs similarity index 93% rename from Content.Server/Administration/BanCommand.cs rename to Content.Server/Administration/Commands/BanCommand.cs index 2dbabd2302..f542d1c0c2 100644 --- a/Content.Server/Administration/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -1,6 +1,6 @@ using System; -using System.Net; using Content.Server.Database; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.IoC; @@ -8,8 +8,9 @@ using Robust.Shared.Network; #nullable enable -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Ban)] public sealed class BanCommand : IClientCommand { public string Command => "ban"; diff --git a/Content.Server/Administration/ControlMob.cs b/Content.Server/Administration/Commands/ControlMob.cs similarity index 94% rename from Content.Server/Administration/ControlMob.cs rename to Content.Server/Administration/Commands/ControlMob.cs index 317f2d77ee..1e979e633a 100644 --- a/Content.Server/Administration/ControlMob.cs +++ b/Content.Server/Administration/Commands/ControlMob.cs @@ -1,6 +1,7 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; using Content.Server.Players; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; @@ -8,8 +9,9 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] class ControlMob : IClientCommand { public string Command => "controlmob"; diff --git a/Content.Server/Administration/Commands/DeAdminCommand.cs b/Content.Server/Administration/Commands/DeAdminCommand.cs new file mode 100644 index 0000000000..44135a0c1b --- /dev/null +++ b/Content.Server/Administration/Commands/DeAdminCommand.cs @@ -0,0 +1,31 @@ +using Content.Shared.Administration; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.IoC; + +#nullable enable + +namespace Content.Server.Administration +{ + [UsedImplicitly] + [AdminCommand(AdminFlags.None)] + public class DeAdminCommand : IClientCommand + { + public string Command => "deadmin"; + public string Description => "Temporarily de-admins you so you can experience the round as a normal player."; + public string Help => "Usage: deadmin\nUse readmin to re-admin after using this."; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player == null) + { + shell.SendText(player, "You cannot use this command from the server console."); + return; + } + + var mgr = IoCManager.Resolve(); + mgr.DeAdmin(player); + } + } +} diff --git a/Content.Server/Administration/DeleteEntitiesWithComponent.cs b/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs similarity index 93% rename from Content.Server/Administration/DeleteEntitiesWithComponent.cs rename to Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs index 0df4af06b8..f87bd7a99b 100644 --- a/Content.Server/Administration/DeleteEntitiesWithComponent.cs +++ b/Content.Server/Administration/Commands/DeleteEntitiesWithComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; @@ -7,8 +8,9 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] class DeleteEntitiesWithComponent : IClientCommand { public string Command => "deleteewc"; diff --git a/Content.Server/Administration/DeleteEntitiesWithId.cs b/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs similarity index 90% rename from Content.Server/Administration/DeleteEntitiesWithId.cs rename to Content.Server/Administration/Commands/DeleteEntitiesWithId.cs index b2f3448167..d7ff635c10 100644 --- a/Content.Server/Administration/DeleteEntitiesWithId.cs +++ b/Content.Server/Administration/Commands/DeleteEntitiesWithId.cs @@ -1,12 +1,14 @@ #nullable enable +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] public class DeleteEntitiesWithId : IClientCommand { public string Command => "deleteewi"; diff --git a/Content.Server/Administration/Commands/ReAdminCommand.cs b/Content.Server/Administration/Commands/ReAdminCommand.cs new file mode 100644 index 0000000000..edd197b71f --- /dev/null +++ b/Content.Server/Administration/Commands/ReAdminCommand.cs @@ -0,0 +1,35 @@ +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.IoC; + +#nullable enable + +namespace Content.Server.Administration +{ + [AnyCommand] + public class ReAdminCommand : IClientCommand + { + public string Command => "readmin"; + public string Description => "Re-admins you if you previously de-adminned."; + public string Help => "Usage: readmin"; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player == null) + { + shell.SendText(player, "You cannot use this command from the server console."); + return; + } + + var mgr = IoCManager.Resolve(); + + if (mgr.GetAdminData(player, includeDeAdmin: true) == null) + { + shell.SendText(player, "You're not an admin."); + return; + } + + mgr.ReAdmin(player); + } + } +} diff --git a/Content.Server/Administration/ReadyAll.cs b/Content.Server/Administration/Commands/ReadyAll.cs similarity index 90% rename from Content.Server/Administration/ReadyAll.cs rename to Content.Server/Administration/Commands/ReadyAll.cs index 05d0b48fc8..ec703bb5b7 100644 --- a/Content.Server/Administration/ReadyAll.cs +++ b/Content.Server/Administration/Commands/ReadyAll.cs @@ -1,12 +1,14 @@ #nullable enable using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.IoC; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Server)] public class ReadyAll : IClientCommand { public string Command => "readyall"; diff --git a/Content.Server/Administration/Rejuvenate.cs b/Content.Server/Administration/Commands/Rejuvenate.cs similarity index 93% rename from Content.Server/Administration/Rejuvenate.cs rename to Content.Server/Administration/Commands/Rejuvenate.cs index deb59d4260..69c79a36b8 100644 --- a/Content.Server/Administration/Rejuvenate.cs +++ b/Content.Server/Administration/Commands/Rejuvenate.cs @@ -1,4 +1,5 @@ using Content.Server.GlobalVerbs; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; @@ -6,8 +7,9 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] class Rejuvenate : IClientCommand { public string Command => "rejuvenate"; diff --git a/Content.Server/Administration/WarpCommand.cs b/Content.Server/Administration/Commands/WarpCommand.cs similarity index 97% rename from Content.Server/Administration/WarpCommand.cs rename to Content.Server/Administration/Commands/WarpCommand.cs index 45fb041a40..6e48b9637d 100644 --- a/Content.Server/Administration/WarpCommand.cs +++ b/Content.Server/Administration/Commands/WarpCommand.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Markers; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Enums; @@ -10,8 +11,9 @@ using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; -namespace Content.Server.Administration +namespace Content.Server.Administration.Commands { + [AdminCommand(AdminFlags.Admin)] public class WarpCommand : IClientCommand { public string Command => "warp"; diff --git a/Content.Server/Administration/IAdminManager.cs b/Content.Server/Administration/IAdminManager.cs new file mode 100644 index 0000000000..e475ebeec8 --- /dev/null +++ b/Content.Server/Administration/IAdminManager.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Content.Shared.Administration; +using Robust.Server.Interfaces.Player; + +#nullable enable + +namespace Content.Server.Administration +{ + /// + /// Manages server administrators and their permission flags. + /// + public interface IAdminManager + { + /// + /// Gets all active admins currently on the server. + /// + /// + /// This does not include admins that are de-adminned. + /// + IEnumerable ActiveAdmins { get; } + + /// + /// Gets the admin data for a player, if they are an admin. + /// + /// The player to get admin data for. + /// + /// Whether to return admin data for admins that are current de-adminned. + /// + /// if the player is not an admin. + AdminData? GetAdminData(IPlayerSession session, bool includeDeAdmin = false); + + /// + /// De-admins an admin temporarily so they are effectively a normal player. + /// + /// + /// De-adminned admins are able to re-admin at any time if they so desire. + /// + void DeAdmin(IPlayerSession session); + + /// + /// Re-admins a de-adminned admin. + /// + void ReAdmin(IPlayerSession session); + + void Initialize(); + } +} diff --git a/Content.Server/Atmos/AtmosCommands.cs b/Content.Server/Atmos/AtmosCommands.cs index 3785e7fe85..476849febf 100644 --- a/Content.Server/Atmos/AtmosCommands.cs +++ b/Content.Server/Atmos/AtmosCommands.cs @@ -1,8 +1,10 @@ #nullable enable using System; +using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems.Atmos; +using Content.Shared.Administration; using Content.Shared.Atmos; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; @@ -15,6 +17,7 @@ using Robust.Shared.Maths; namespace Content.Server.Atmos { + [AdminCommand(AdminFlags.Debug)] public class AddAtmos : IClientCommand { public string Command => "addatmos"; diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs index 94aa2ae25c..e2d32aa25c 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Content.Server.Administration; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Observer; @@ -8,6 +9,7 @@ using Content.Server.Interfaces.GameObjects; using Content.Server.Observer; using Content.Server.Players; using Content.Server.Utility; +using Content.Shared.Administration; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.Interfaces; @@ -20,6 +22,7 @@ using Robust.Shared.Localization; namespace Content.Server.Chat { + [AnyCommand] internal class SayCommand : IClientCommand { public string Command => "say"; @@ -51,6 +54,7 @@ namespace Content.Server.Chat } } + [AnyCommand] internal class MeCommand : IClientCommand { public string Command => "me"; @@ -76,6 +80,7 @@ namespace Content.Server.Chat } } + [AnyCommand] internal class OOCCommand : IClientCommand { public string Command => "ooc"; @@ -96,6 +101,7 @@ namespace Content.Server.Chat } } + [AdminCommand(AdminFlags.Admin)] internal class AdminChatCommand : IClientCommand { public string Command => "asay"; @@ -116,6 +122,7 @@ namespace Content.Server.Chat } } + [AnyCommand] internal class SuicideCommand : IClientCommand { public string Command => "suicide"; diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index 6550d58fb5..df2b087372 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Content.Server.GameObjects.Components; +using Content.Server.Administration; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Headset; using Content.Server.GameObjects.Components.Items.Storage; @@ -12,7 +12,6 @@ using Content.Shared.Chat; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; -using Robust.Server.Console; using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Systems; @@ -47,7 +46,7 @@ namespace Content.Server.Chat [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IMoMMILink _mommiLink = default!; - [Dependency] private readonly IConGroupController _conGroupController = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; public void Initialize() { @@ -231,12 +230,7 @@ namespace Content.Server.Chat return; } - if (!_conGroupController.CanCommand(player, "asay")) - { - SendOOC(player, message); - return; - } - var clients = _playerManager.GetPlayersBy(x => _conGroupController.CanCommand(x, "asay")).Select(p => p.ConnectedClient);; + var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); var msg = _netManager.CreateNetMessage(); diff --git a/Content.Server/Commands/AttachBodyPartCommand.cs b/Content.Server/Commands/AttachBodyPartCommand.cs index b19c829611..72128390b5 100644 --- a/Content.Server/Commands/AttachBodyPartCommand.cs +++ b/Content.Server/Commands/AttachBodyPartCommand.cs @@ -1,5 +1,7 @@ #nullable enable +using Content.Server.Administration; using Content.Server.GameObjects.Components.Body.Part; +using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; using Robust.Server.Interfaces.Console; @@ -10,6 +12,7 @@ using Robust.Shared.IoC; namespace Content.Server.Commands { + [AdminCommand(AdminFlags.Fun)] public class AttachBodyPartCommand : IClientCommand { public string Command => "attachbodypart"; diff --git a/Content.Server/Commands/HideContainedContextCommand.cs b/Content.Server/Commands/HideContainedContextCommand.cs index a4c7393146..ad975313a1 100644 --- a/Content.Server/Commands/HideContainedContextCommand.cs +++ b/Content.Server/Commands/HideContainedContextCommand.cs @@ -1,11 +1,14 @@ #nullable enable +using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands { + [AdminCommand(AdminFlags.Debug)] public class HideContainedContextCommand : IClientCommand { public string Command => "hidecontainedcontext"; diff --git a/Content.Server/Commands/ShowContainedContextCommand.cs b/Content.Server/Commands/ShowContainedContextCommand.cs index 3b37d6375e..8afcc5501b 100644 --- a/Content.Server/Commands/ShowContainedContextCommand.cs +++ b/Content.Server/Commands/ShowContainedContextCommand.cs @@ -1,11 +1,14 @@ #nullable enable +using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Systems; namespace Content.Server.Commands { + [AdminCommand(AdminFlags.Debug)] public class ShowContainedContextCommand : IClientCommand { public const string CommandName = "showcontainedcontext"; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index dbc1cc1231..7c0a13af63 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -217,6 +217,15 @@ namespace Content.Server.Database */ public abstract Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + /* + * ADMIN STUFF + */ + public async Task GetAdminDataForAsync(NetUserId userId) + { + await using var db = await GetDb(); + + return await db.DbContext.Admin.SingleOrDefaultAsync(p => p.UserId == userId.UserId); + } protected abstract Task GetDb(); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 8d7dc816c4..f7eb67ee09 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -45,6 +45,9 @@ namespace Content.Server.Database // Connection log Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + + // Admins + Task GetAdminDataForAsync(NetUserId userId); } public sealed class ServerDbManager : IServerDbManager @@ -137,6 +140,11 @@ namespace Content.Server.Database return _db.AddConnectionLogAsync(userId, userName, address); } + public Task GetAdminDataForAsync(NetUserId userId) + { + return _db.GetAdminDataForAsync(userId); + } + private DbContextOptions CreatePostgresOptions() { var host = _cfg.GetCVar(CCVars.DatabasePgHost); diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 9542e2b36a..3bc16dee3a 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -1,4 +1,5 @@ -using Content.Server.AI.Utility.Considerations; +using Content.Server.Administration; +using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; using Content.Server.Database; using Content.Server.GameObjects.Components.Mobs.Speech; @@ -77,6 +78,7 @@ namespace Content.Server IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); } public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) diff --git a/Content.Server/GameObjects/Components/Body/BodyCommands.cs b/Content.Server/GameObjects/Components/Body/BodyCommands.cs index 867ad77f9f..d0129b7f1a 100644 --- a/Content.Server/GameObjects/Components/Body/BodyCommands.cs +++ b/Content.Server/GameObjects/Components/Body/BodyCommands.cs @@ -1,6 +1,8 @@ #nullable enable using System; using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body.Part; @@ -16,6 +18,7 @@ using Robust.Shared.Random; namespace Content.Server.GameObjects.Components.Body { + [AdminCommand(AdminFlags.Fun)] class AddHandCommand : IClientCommand { public const string DefaultHandPrototype = "LeftHandHuman"; @@ -149,6 +152,7 @@ namespace Content.Server.GameObjects.Components.Body } } + [AdminCommand(AdminFlags.Fun)] class RemoveHandCommand : IClientCommand { public string Command => "removehand"; @@ -190,6 +194,7 @@ namespace Content.Server.GameObjects.Components.Body } } + [AdminCommand(AdminFlags.Fun)] class DestroyMechanismCommand : IClientCommand { public string Command => "destroymechanism"; @@ -242,6 +247,7 @@ namespace Content.Server.GameObjects.Components.Body } } + [AdminCommand(AdminFlags.Fun)] class HurtCommand : IClientCommand { public string Command => "hurt"; diff --git a/Content.Server/GameObjects/Components/Damage/DamageCommands.cs b/Content.Server/GameObjects/Components/Damage/DamageCommands.cs index d691892293..e90fc5d294 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageCommands.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageCommands.cs @@ -1,7 +1,9 @@ #nullable enable using System; using System.Diagnostics.CodeAnalysis; +using Content.Server.Administration; using Content.Server.GameObjects.Components.Atmos; +using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Damage; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; @@ -115,6 +117,7 @@ namespace Content.Server.GameObjects.Components.Damage } } + [AdminCommand(AdminFlags.Fun)] public class AddDamageFlagCommand : DamageFlagCommand { public override string Command => "adddamageflag"; @@ -133,6 +136,7 @@ namespace Content.Server.GameObjects.Components.Damage } } + [AdminCommand(AdminFlags.Fun)] public class RemoveDamageFlagCommand : DamageFlagCommand { public override string Command => "removedamageflag"; @@ -151,6 +155,7 @@ namespace Content.Server.GameObjects.Components.Damage } } + [AdminCommand(AdminFlags.Admin)] public class GodModeCommand : IClientCommand { public string Command => "godmode"; diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs b/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs index 238b31c737..2f549a8037 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs @@ -1,4 +1,6 @@ #nullable enable +using Content.Server.Administration; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; @@ -8,6 +10,7 @@ using Robust.Shared.Localization; namespace Content.Server.GameObjects.Components.Disposal { + [AdminCommand(AdminFlags.Debug)] public class TubeConnectionsCommand : IClientCommand { public string Command => "tubeconnections"; diff --git a/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs b/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs index c4ec4a2a05..18faf899b6 100644 --- a/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs +++ b/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs @@ -1,5 +1,7 @@ #nullable enable using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Server.Interfaces.Console; @@ -14,7 +16,7 @@ namespace Content.Server.GameObjects.Components.Interactable /// /// /// - [UsedImplicitly] + [AdminCommand(AdminFlags.Debug)] class TilePryCommand : IClientCommand { public string Command => "tilepry"; @@ -69,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Interactable } } - [UsedImplicitly] + [AdminCommand(AdminFlags.Debug)] class AnchorCommand : IClientCommand { public string Command => "anchor"; @@ -114,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Interactable } } - [UsedImplicitly] + [AdminCommand(AdminFlags.Debug)] class UnAnchorCommand : IClientCommand { public string Command => "unanchor"; diff --git a/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs b/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs index 1140770e9d..3af74dbaa0 100644 --- a/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/Speech/SpeechComponent.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using Content.Server.Administration; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; @@ -17,6 +19,7 @@ namespace Content.Server.GameObjects.Components.Mobs.Speech public string Accentuate(string message); } + [AdminCommand(AdminFlags.Fun)] public class AddAccent : IClientCommand { public string Command => "addaccent"; @@ -38,12 +41,12 @@ namespace Content.Server.GameObjects.Components.Mobs.Speech shell.SendText(player, "You don't have a player!"); return; } - + var compFactory = IoCManager.Resolve(); - + if (args[0] == "?") { - // Get all components that implement the ISpeechComponent except + // Get all components that implement the ISpeechComponent except var speeches = compFactory.GetAllRefTypes() .Where(c => typeof(IAccentComponent).IsAssignableFrom(c) && c.IsClass); var msg = ""; diff --git a/Content.Server/GameObjects/Components/Observer/GhostComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs index 1a36f8fd8c..8fd45b1bdc 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs @@ -102,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Observer { if (player.AttachedEntity != null && warp.PlayerTarget == player.AttachedEntity.Uid) { - session?.AttachedEntity!.Transform.Coordinates = + session!.AttachedEntity!.Transform.Coordinates = player.AttachedEntity.Transform.Coordinates; } } @@ -113,7 +113,7 @@ namespace Content.Server.GameObjects.Components.Observer { if (warp.WarpName == warpPoint.Location) { - session?.AttachedEntity!.Transform.Coordinates = warpPoint.Owner.Transform.Coordinates ; + session!.AttachedEntity!.Transform.Coordinates = warpPoint.Owner.Transform.Coordinates ; } } } diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs index e12fc1633e..ea35dceb99 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiFactionTagSystem.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Text; +using Content.Server.Administration; using Content.Server.GameObjects.Components.AI; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Systems; @@ -19,12 +21,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI * Currently factions are implicitly friendly if they are not hostile. * This may change where specified friendly factions are listed. (e.g. to get number of friendlies in area). */ - + public Faction GetHostileFactions(Faction faction) => _hostileFactions.TryGetValue(faction, out var hostiles) ? hostiles : Faction.None; - + private Dictionary _hostileFactions = new Dictionary { - {Faction.NanoTransen, + {Faction.NanoTransen, Faction.SimpleHostile | Faction.Syndicate | Faction.Xeno}, {Faction.SimpleHostile, Faction.NanoTransen | Faction.Syndicate @@ -35,11 +37,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI }, {Faction.Syndicate, Faction.NanoTransen | Faction.SimpleHostile | Faction.Xeno}, - {Faction.Xeno, + {Faction.Xeno, Faction.NanoTransen | Faction.Syndicate}, }; - public Faction GetFactions(IEntity entity) => + public Faction GetFactions(IEntity entity) => entity.TryGetComponent(out AiFactionTagComponent factionTags) ? factionTags.Factions : Faction.None; @@ -76,7 +78,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI hostileFactions &= ~target; _hostileFactions[source] = hostileFactions; } - + public void MakeHostile(Faction source, Faction target) { if (!_hostileFactions.TryGetValue(source, out var hostileFactions)) @@ -89,12 +91,13 @@ namespace Content.Server.GameObjects.EntitySystems.AI _hostileFactions[source] = hostileFactions; } } - + + [AdminCommand(AdminFlags.Fun)] public sealed class FactionCommand : IClientCommand { public string Command => "factions"; public string Description => "Update / list factional relationships for NPCs."; - public string Help => "faction target\n" + + public string Help => "faction target\n" + "faction list: hostile factions"; public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) @@ -108,11 +111,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI continue; result.Append(value + "\n"); } - + shell.SendText(player, result.ToString()); return; } - + if (args.Length < 2) { shell.SendText(player, Loc.GetString("Need more args")); @@ -141,7 +144,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI shell.SendText(player, Loc.GetString("Invalid target faction")); return; } - + EntitySystem.Get().MakeFriendly(faction, targetFaction); shell.SendText(player, Loc.GetString("Command successful")); break; @@ -157,7 +160,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI shell.SendText(player, Loc.GetString("Invalid target faction")); return; } - + EntitySystem.Get().MakeHostile(faction, targetFaction); shell.SendText(player, Loc.GetString("Command successful")); break; @@ -172,4 +175,4 @@ namespace Content.Server.GameObjects.EntitySystems.AI return; } } -} \ No newline at end of file +} diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index f951020547..abb8876ab3 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Server.Administration; using Content.Server.GameObjects.Components.Movement; +using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Movement; using JetBrains.Annotations; using Robust.Server.AI; @@ -75,7 +77,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI break; case false: _awakeAi.Add(message.Processor); - + if (_awakeAi.Count > cvarMaxUpdates) { Logger.Warning($"AI limit exceeded: {_awakeAi.Count} / {cvarMaxUpdates}"); @@ -101,7 +103,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI toRemove.Add(processor); continue; } - + processor.Update(frameTime); count++; } @@ -145,6 +147,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI public bool ProcessorTypeExists(string name) => _processorTypes.ContainsKey(name); + [AdminCommand(AdminFlags.Fun)] private class AddAiCommand : IClientCommand { public string Command => "addai"; diff --git a/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs b/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs index a9c58b076a..88782c51a4 100644 --- a/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/SignalLinkerSystem.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using Content.Server.Administration; using Content.Server.GameObjects.Components.MachineLinking; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; @@ -96,6 +98,7 @@ namespace Content.Server.GameObjects.EntitySystems } + [AdminCommand(AdminFlags.Debug)] public class SignalLinkerCommand : IClientCommand { public string Command => "signallink"; diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index b6d396e95e..6b83a46088 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using Content.Server.Administration; using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index c84e448785..6fa0f2641a 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Server.Administration; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; +using Content.Shared.Administration; using Content.Shared.Maps; using Content.Shared.Roles; using Robust.Server.Interfaces.Console; @@ -18,6 +20,7 @@ using Robust.Shared.Utility; namespace Content.Server.GameTicking { + [AdminCommand(AdminFlags.Server)] class DelayStartCommand : IClientCommand { public string Command => "delaystart"; @@ -60,6 +63,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] class StartRoundCommand : IClientCommand { public string Command => "startround"; @@ -80,6 +84,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] class EndRoundCommand : IClientCommand { public string Command => "endround"; @@ -100,6 +105,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] public class NewRoundCommand : IClientCommand { public string Command => "restartround"; @@ -165,6 +171,7 @@ namespace Content.Server.GameTicking } } + [AnyCommand] class ObserveCommand : IClientCommand { public string Command => "observe"; @@ -183,6 +190,7 @@ namespace Content.Server.GameTicking } } + [AnyCommand] class JoinGameCommand : IClientCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -228,6 +236,7 @@ namespace Content.Server.GameTicking } } + [AnyCommand] class ToggleReadyCommand : IClientCommand { public string Command => "toggleready"; @@ -246,6 +255,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] class ToggleDisallowLateJoinCommand: IClientCommand { public string Command => "toggledisallowlatejoin"; @@ -274,6 +284,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] class SetGamePresetCommand : IClientCommand { public string Command => "setgamepreset"; @@ -294,6 +305,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server)] class ForcePresetCommand : IClientCommand { public string Command => "forcepreset"; @@ -327,6 +339,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Server | AdminFlags.Mapping)] class MappingCommand : IClientCommand { public string Command => "mapping"; @@ -383,6 +396,7 @@ namespace Content.Server.GameTicking } } + [AdminCommand(AdminFlags.Mapping)] class TileWallsCommand : IClientCommand { // ReSharper disable once StringLiteralTypo diff --git a/Content.Server/Mobs/Commands.cs b/Content.Server/Mobs/Commands.cs index 6d64578411..45af6e11b6 100644 --- a/Content.Server/Mobs/Commands.cs +++ b/Content.Server/Mobs/Commands.cs @@ -1,7 +1,9 @@ using System.Text; +using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Server.Mobs.Roles; using Content.Server.Players; +using Content.Shared.Administration; using Content.Shared.Roles; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; @@ -12,6 +14,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Mobs { + [AdminCommand(AdminFlags.Admin)] public class MindInfoCommand : IClientCommand { public string Command => "mindinfo"; @@ -49,6 +52,7 @@ namespace Content.Server.Mobs } } + [AdminCommand(AdminFlags.Fun)] public class AddRoleCommand : IClientCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -81,6 +85,7 @@ namespace Content.Server.Mobs } } + [AdminCommand(AdminFlags.Fun)] public class RemoveRoleCommand : IClientCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -113,6 +118,7 @@ namespace Content.Server.Mobs } } + [AdminCommand(AdminFlags.Debug)] public class AddOverlayCommand : IClientCommand { public string Command => "addoverlay"; @@ -137,6 +143,7 @@ namespace Content.Server.Mobs } } + [AdminCommand(AdminFlags.Debug)] public class RemoveOverlayCommand : IClientCommand { public string Command => "rmoverlay"; diff --git a/Content.Server/Observer/Ghost.cs b/Content.Server/Observer/Ghost.cs index 8e97025f66..6aca5cff89 100644 --- a/Content.Server/Observer/Ghost.cs +++ b/Content.Server/Observer/Ghost.cs @@ -1,3 +1,4 @@ +using Content.Server.Administration; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.GameTicking; @@ -12,6 +13,7 @@ using Robust.Shared.IoC; namespace Content.Server.Observer { + [AnyCommand] public class Ghost : IClientCommand { public string Command => "ghost"; diff --git a/Content.Server/Players/PlayerData.cs b/Content.Server/Players/PlayerData.cs index c66b6f7024..d398d87747 100644 --- a/Content.Server/Players/PlayerData.cs +++ b/Content.Server/Players/PlayerData.cs @@ -25,6 +25,12 @@ namespace Content.Server.Players [ViewVariables] public Mind? Mind { get; set; } + /// + /// If true, the player is an admin and they explicitly de-adminned mid-game, + /// so they should not regain admin if they reconnect. + /// + public bool ExplicitlyDeadminned { get; set; } + public void WipeMind() { Mind?.ChangeOwningPlayer(null); diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index dadb213851..abde376c85 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -1,4 +1,5 @@ -using Content.Server.AI.Utility.Considerations; +using Content.Server.Administration; +using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; using Content.Server.Cargo; using Content.Server.Chat; @@ -44,6 +45,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/ServerNotifyManager.cs b/Content.Server/ServerNotifyManager.cs index 7dd9984245..aac886c0aa 100644 --- a/Content.Server/ServerNotifyManager.cs +++ b/Content.Server/ServerNotifyManager.cs @@ -1,5 +1,7 @@ +using Content.Server.Administration; using Content.Server.Interfaces; using Content.Shared; +using Content.Shared.Administration; using Content.Shared.Interfaces; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.GameObjects; @@ -71,6 +73,7 @@ namespace Content.Server _netManager.ServerSendMessage(netMessage, actor.playerSession.ConnectedClient); } + [AdminCommand(AdminFlags.Debug)] public class PopupMsgCommand : IClientCommand { public string Command => "srvpopupmsg"; diff --git a/Content.Server/StationEvents/StationEventCommand.cs b/Content.Server/StationEvents/StationEventCommand.cs index 108baba460..2ec3587142 100644 --- a/Content.Server/StationEvents/StationEventCommand.cs +++ b/Content.Server/StationEvents/StationEventCommand.cs @@ -1,6 +1,7 @@ #nullable enable +using Content.Server.Administration; using Content.Server.GameObjects.EntitySystems.StationEvents; -using JetBrains.Annotations; +using Content.Shared.Administration; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Systems; @@ -8,14 +9,14 @@ using Robust.Shared.Localization; namespace Content.Server.StationEvents { - [UsedImplicitly] + [AdminCommand(AdminFlags.Server)] public sealed class StationEventCommand : IClientCommand { public string Command => "events"; public string Description => "Provides admin control to station events"; - public string Help => "events >\n" + + public string Help => "events >\n" + "list: return all event names that can be run\n " + - "pause: stop all random events from running\n" + + "pause: stop all random events from running\n" + "resume: allow random events to run again\n" + "run: start a particular event now; is case-insensitive and not localized"; public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) @@ -25,19 +26,19 @@ namespace Content.Server.StationEvents shell.SendText(player, "Need more args"); return; } - + if (args[0] == "list") { var resultText = "Random\n" + EntitySystem.Get().GetEventNames(); shell.SendText(player, resultText); return; } - + // Didn't use a "toggle" so it's explicit if (args[0] == "pause") { var stationEventSystem = EntitySystem.Get(); - + if (!stationEventSystem.Enabled) { shell.SendText(player, Loc.GetString("Station events are already paused")); @@ -50,11 +51,11 @@ namespace Content.Server.StationEvents return; } } - + if (args[0] == "resume") { var stationEventSystem = EntitySystem.Get(); - + if (stationEventSystem.Enabled) { shell.SendText(player, Loc.GetString("Station events are already running")); @@ -88,7 +89,7 @@ namespace Content.Server.StationEvents { resultText = EntitySystem.Get().RunEvent(eventName); } - + shell.SendText(player, resultText); return; } diff --git a/Content.Shared/Administration/AdminData.cs b/Content.Shared/Administration/AdminData.cs new file mode 100644 index 0000000000..70ff7c6876 --- /dev/null +++ b/Content.Shared/Administration/AdminData.cs @@ -0,0 +1,39 @@ +#nullable enable + +namespace Content.Shared.Administration +{ + public sealed class AdminData + { + public const string DefaultTitle = "Admin"; + + // Can be false if they're de-adminned with the ability to re-admin. + public bool Active; + public string? Title; + public AdminFlags Flags; + + public bool HasFlag(AdminFlags flag) + { + return Active && (Flags & flag) == flag; + } + + public bool CanViewVar() + { + return HasFlag(AdminFlags.VarEdit); + } + + public bool CanAdminPlace() + { + return HasFlag(AdminFlags.Spawn); + } + + public bool CanScript() + { + return HasFlag(AdminFlags.Host); + } + + public bool CanAdminMenu() + { + return HasFlag(AdminFlags.Admin); + } + } +} diff --git a/Content.Shared/Administration/AdminFlags.cs b/Content.Shared/Administration/AdminFlags.cs new file mode 100644 index 0000000000..4b3a856c55 --- /dev/null +++ b/Content.Shared/Administration/AdminFlags.cs @@ -0,0 +1,68 @@ +using System; + +namespace Content.Shared.Administration +{ + /// + /// Permissions that admins can have. + /// + [Flags] + public enum AdminFlags : uint + { + None = 0, + + /// + /// Basic admin verbs. + /// + Admin = 1 << 0, + + /// + /// Ability to ban people. + /// + Ban = 1 << 1, + + /// + /// Debug commands for coders. + /// + Debug = 1 << 2, + + /// + /// !!FUN!! + /// + Fun = 1 << 3, + + /// + /// Ability to edit permissions for other administrators. + /// + Permissions = 1 << 4, + + /// + /// Ability to control teh server like restart it or change the round type. + /// + Server = 1 << 5, + + /// + /// Ability to spawn stuff in. + /// + Spawn = 1 << 6, + + /// + /// Ability to use VV. + /// + VarEdit = 1 << 7, + + /// + /// Large mapping operations. + /// + Mapping = 1 << 8, + + /// + /// Makes you british. + /// + Piss = 1 << 9, + + /// + /// Dangerous host permissions like scsi. + /// + Host = 1u << 31, + } +} diff --git a/Content.Shared/Administration/AdminFlagsExt.cs b/Content.Shared/Administration/AdminFlagsExt.cs new file mode 100644 index 0000000000..abe198a1f0 --- /dev/null +++ b/Content.Shared/Administration/AdminFlagsExt.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Content.Shared.Administration +{ + public static class AdminFlagsExt + { + private static readonly Dictionary NameFlagsMap = new Dictionary(); + private static readonly string[] FlagsNameMap = new string[32]; + + public static readonly AdminFlags Everything; + + static AdminFlagsExt() + { + var t = typeof(AdminFlags); + var flags = (AdminFlags[]) Enum.GetValues(t); + + foreach (var value in flags) + { + var name = value.ToString().ToUpper(); + + if (BitOperations.PopCount((uint) value) != 1) + { + continue; + } + + Everything |= value; + NameFlagsMap.Add(name, value); + FlagsNameMap[BitOperations.Log2((uint) value)] = name; + } + } + + public static AdminFlags NamesToFlags(IEnumerable names) + { + var flags = AdminFlags.None; + foreach (var name in names) + { + if (!NameFlagsMap.TryGetValue(name, out var value)) + { + throw new ArgumentException($"Invalid admin flag name: {name}"); + } + + flags |= value; + } + + return flags; + } + + public static AdminFlags NameToFlag(string name) + { + return NameFlagsMap[name]; + } + + public static string[] FlagsToNames(AdminFlags flags) + { + var array = new string[BitOperations.PopCount((uint) flags)]; + var highest = BitOperations.LeadingZeroCount((uint) flags); + + var ai = 0; + for (var i = 0; i < 32 - highest; i++) + { + var flagValue = (AdminFlags) (1u << i); + if ((flags & flagValue) != 0) + { + array[ai++] = FlagsNameMap[i]; + } + } + + return array; + } + } +} diff --git a/Content.Shared/CCVars.cs b/Content.Shared/CCVars.cs index d01c1d9c19..02d58f26bd 100644 --- a/Content.Shared/CCVars.cs +++ b/Content.Shared/CCVars.cs @@ -34,6 +34,9 @@ namespace Content.Shared public static readonly CVarDef GamePersistGuests = CVarDef.Create("game.persistguests", true, CVar.ARCHIVE | CVar.SERVERONLY); + public static readonly CVarDef + ConsoleLoginLocal = CVarDef.Create("console.loginlocal", true, CVar.ARCHIVE); + /* * Database stuff diff --git a/Content.Shared/Network/NetMessages/MsgUpdateAdminStatus.cs b/Content.Shared/Network/NetMessages/MsgUpdateAdminStatus.cs new file mode 100644 index 0000000000..bdeb4be645 --- /dev/null +++ b/Content.Shared/Network/NetMessages/MsgUpdateAdminStatus.cs @@ -0,0 +1,73 @@ +using Content.Shared.Administration; +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Network; + +namespace Content.Shared.Network.NetMessages +{ + public sealed class MsgUpdateAdminStatus : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgUpdateAdminStatus); + + public MsgUpdateAdminStatus(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + public AdminData Admin; + public string[] AvailableCommands; + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + var count = buffer.ReadVariableInt32(); + + AvailableCommands = new string[count]; + + for (var i = 0; i < count; i++) + { + AvailableCommands[i] = buffer.ReadString(); + } + + if (buffer.ReadBoolean()) + { + var active = buffer.ReadBoolean(); + buffer.ReadPadBits(); + var flags = (AdminFlags) buffer.ReadUInt32(); + var title = buffer.ReadString(); + + Admin = new AdminData + { + Active = active, + Title = title, + Flags = flags, + }; + } + + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + buffer.WriteVariableInt32(AvailableCommands.Length); + + foreach (var cmd in AvailableCommands) + { + buffer.Write(cmd); + } + + var isAdmin = Admin != null; + buffer.Write(isAdmin); + + if (isAdmin) + { + buffer.Write(Admin.Active); + buffer.WritePadBits(); + buffer.Write((uint) Admin.Flags); + buffer.Write(Admin.Title); + } + } + + public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableOrdered; + } +} diff --git a/Content.Tests/Shared/Administration/AdminFlagsExtTest.cs b/Content.Tests/Shared/Administration/AdminFlagsExtTest.cs new file mode 100644 index 0000000000..e44a8a1719 --- /dev/null +++ b/Content.Tests/Shared/Administration/AdminFlagsExtTest.cs @@ -0,0 +1,35 @@ +using System; +using Content.Shared.Administration; +using NUnit.Framework; + +namespace Content.Tests.Shared.Administration +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class AdminFlagsExtTest + { + [Test] + [TestCase("ADMIN", AdminFlags.Admin)] + [TestCase("ADMIN,DEBUG", AdminFlags.Admin | AdminFlags.Debug)] + [TestCase("ADMIN,DEBUG,HOST", AdminFlags.Admin | AdminFlags.Debug | AdminFlags.Host)] + [TestCase("", AdminFlags.None)] + public void TestNamesToFlags(string namesConcat, AdminFlags flags) + { + var names = namesConcat.Split(",", StringSplitOptions.RemoveEmptyEntries); + + Assert.That(AdminFlagsExt.NamesToFlags(names), Is.EqualTo(flags)); + } + + [Test] + [TestCase("ADMIN", AdminFlags.Admin)] + [TestCase("ADMIN,DEBUG", AdminFlags.Admin | AdminFlags.Debug)] + [TestCase("ADMIN,DEBUG,HOST", AdminFlags.Admin | AdminFlags.Debug | AdminFlags.Host)] + [TestCase("", AdminFlags.None)] + public void TestFlagsToNames(string namesConcat, AdminFlags flags) + { + var names = namesConcat.Split(",", StringSplitOptions.RemoveEmptyEntries); + + Assert.That(AdminFlagsExt.FlagsToNames(flags), Is.EquivalentTo(names)); + } + } +} diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml deleted file mode 100644 index 916f74d526..0000000000 --- a/Resources/Groups/groups.yml +++ /dev/null @@ -1,233 +0,0 @@ -- Index: 1 - Name: Player - Commands: - - login - - joingame - - help - - list - - say - - whisper - - me - - ooc - - observe - - toggleready - - ghost - - suicide - - hostlogin - -- Index: 50 - Name: Moderator - Commands: - - login - - logout - - joingame - - help - - list - - say - - whisper - - me - - ooc - - showtime - - observe - - toggleready - - ghost - - suicide - - kick - - listplayers - - loc - - hostlogin - - events - - factions - CanAdminMenu: true - -- Index: 100 - Name: Administrator - Commands: - - logout - - joingame - - help - - list - - say - - whisper - - me - - ooc - - showtime - - aghost - - observe - - toggleready - - ghost - - suicide - - spawn - - delete - - tp - - tpgrid - - setgamepreset - - forcepreset - - delaystart - - startround - - endround - - restartround - - respawn - - rejuvenate - - addcomp - - rmcomp - - controlmob - - kick - - listplayers - - loc - - lsmap - - lsgrid - - mindinfo - - addrole - - rmrole - - addoverlay - - rmoverlay - - showtime - - group - - addai - - warp - - hostlogin - - deleteewc - - asay - - mapping - - addhand - - removehand - - tilepry - - anchor - - unanchor - - tubeconnections - - tilewalls - - events - - destroymechanism - - addaccent - - readyall - - factions - - signallink - - adddamageflag - - removedamageflag - - godmode - - deleteewi - - hurt - - toggledisallowlatejoin - - showcontainedcontext - - hidecontainedcontext - - showmechanisms - - hidemechanisms - - attachbodypart - - attachtoself - - attachtogrid - - attachtograndparent - CanViewVar: true - CanAdminPlace: true - CanAdminMenu: true - -- Index: 200 - Name: Host - Commands: - - logout - - joingame - - help - - list - - say - - whisper - - me - - ooc - - showtime - - aghost - - observe - - toggleready - - ghost - - suicide - - spawn - - delete - - tp - - tpgrid - - setgamepreset - - forcepreset - - delaystart - - startround - - endround - - restartround - - respawn - - rejuvenate - - addcomp - - controlmob - - kick - - listplayers - - loc - - lsmap - - lsgrid - - mindinfo - - addrole - - rmrole - - addoverlay - - rmoverlay - - srvpopupmsg - - group - - showtime - - restart - - cvar - - netaudit - - szr_stats - - mem - - addai - - loglevel - - testlog - - addmap - - rmmap - - savebp - - loadbp - - savemap - - loadmap - - pausemap - - unpausemap - - querymappaused - - mapinit - - saveconfig - - gc - - gc_mode - - warp - - deleteewc - - sudo - - asay - - mapping - - addhand - - removehand - - tilepry - - anchor - - unanchor - - tubeconnections - - addatmos - - addgas - - fillgas - - listgases - - removegas - - settemp - - setatmostemp - - deletegas - - showatmos - - tilewalls - - events - - destroymechanism - - addaccent - - readyall - - factions - - signallink - - adddamageflag - - removedamageflag - - godmode - - deleteewi - - hurt - - toggledisallowlatejoin - - showcontainedcontext - - hidecontainedcontext - - showmechanisms - - hidemechanisms - - attachbodypart - - attachtoself - - attachtogrid - - attachtograndparent - CanViewVar: true - CanAdminPlace: true - CanScript: true - CanAdminMenu: true diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml new file mode 100644 index 0000000000..80c10aafbf --- /dev/null +++ b/Resources/engineCommandPerms.yml @@ -0,0 +1,63 @@ +# Available to everybody +- Commands: + - help + - list + +- Flags: FUN + Commands: + - addcomp + - rmcomp + +- Flags: DEBUG + Commands: + - delete + - lsgrid + - lsmap + - listplayers + - loc + - mem + - netaudit + - querymappaused + - showtime + +- Flags: MAPPING + Commands: + - addmap + - loadbp + - loadmap + - pausemap + - querymappaused + - rmgrid + - rmmap + - mapinit + - savebp + - savemap + - tpgrid + +- Flags: ADMIN + Commands: + - delete + - kick + - listplayers + - teleport + +- Flags: SERVER + Commands: + - delete + - pausemap + - unpausemap + - restart + - shutdown + +- Flags: SPAWN + Commands: + - spawn + +- Flags: HOST + Commands: + - gc_mode + - gc + - loglevel + - saveconfig + - testlog + - sudo diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 9a400dd8e1..c47b4913df 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -63,6 +63,7 @@ <data /> <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> True + True True True True