From 4825142210d9e74af2177de79a825101f75aea76 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:11:39 -0800 Subject: [PATCH] Role bans (#6703) --- .../20220214061058_RoleBans.Designer.cs | 1041 +++++++++++++++++ .../Postgres/20220214061058_RoleBans.cs | 82 ++ .../PostgresServerDbContextModelSnapshot.cs | 110 ++ .../20220214060745_RoleBans.Designer.cs | 990 ++++++++++++++++ .../Sqlite/20220214060745_RoleBans.cs | 79 ++ .../SqliteServerDbContextModelSnapshot.cs | 104 ++ Content.Server.Database/Model.cs | 35 + Content.Server.Database/ModelPostgres.cs | 8 +- Content.Server.Database/ModelSqlite.cs | 6 + .../Administration/Commands/JobBanCommand.cs | 49 + .../Commands/RoleBanListCommand.cs | 84 ++ .../Administration/Managers/RoleBanManager.cs | 183 +++ Content.Server/Database/ServerDbBase.cs | 31 + Content.Server/Database/ServerDbManager.cs | 55 + Content.Server/Database/ServerDbPostgres.cs | 196 +++- Content.Server/Database/ServerDbSqlite.cs | 157 +++ Content.Server/Database/ServerRoleBanDef.cs | 56 + Content.Server/Database/ServerRoleUnbanDef.cs | 19 + Content.Server/Entry/EntryPoint.cs | 1 + .../GameTicking/GameTicker.GamePreset.cs | 51 + .../GameTicking/GameTicker.JobController.cs | 16 +- .../GameTicking/GameTicker.RoundFlow.cs | 127 +- .../GameTicking/GameTicker.Spawning.cs | 74 +- Content.Server/GameTicking/GameTicker.cs | 4 +- Content.Server/IoC/ServerContentIoC.cs | 1 + 25 files changed, 3429 insertions(+), 130 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.cs create mode 100644 Content.Server/Administration/Commands/JobBanCommand.cs create mode 100644 Content.Server/Administration/Commands/RoleBanListCommand.cs create mode 100644 Content.Server/Administration/Managers/RoleBanManager.cs create mode 100644 Content.Server/Database/ServerRoleBanDef.cs create mode 100644 Content.Server/Database/ServerRoleUnbanDef.cs diff --git a/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.Designer.cs b/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.Designer.cs new file mode 100644 index 0000000000..14621dd08a --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.Designer.cs @@ -0,0 +1,1041 @@ +// +using System; +using System.Net; +using System.Text.Json; +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; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20220214061058_RoleBans")] + partial class RoleBans + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id", "RoundId") + .HasName("PK_admin_log"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_log_round_id"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.Property("Uid") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uid"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Uid")); + + b.Property("AdminLogId") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("AdminLogRoundId") + .HasColumnType("integer") + .HasColumnName("admin_log_round_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Uid") + .HasName("PK_admin_log_entity"); + + b.HasIndex("AdminLogId", "AdminLogRoundId") + .HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id"); + + b.ToTable("admin_log_entity", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("PlayerUserId", "LogId", "RoundId") + .HasName("PK_admin_log_player"); + + b.HasIndex("LogId", "RoundId"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_rank_flag_admin_rank_id"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId") + .HasDatabaseName("IX_job_profile_id"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.HasKey("Id") + .HasName("PK_round"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property?>("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property?>("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("UnbanId") + .HasColumnType("integer") + .HasColumnName("unban_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("UnbanId") + .HasDatabaseName("IX_server_role_ban__unban_id"); + + b.ToTable("server_role_ban", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_role_unban_ban_id"); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.HasOne("Content.Server.Database.AdminLog", null) + .WithMany("Entities") + .HasForeignKey("AdminLogId", "AdminLogRoundId") + .HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("LogId", "RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban") + .WithMany() + .HasForeignKey("UnbanId") + .HasConstraintName("FK_server_role_ban_server_role_unban__unban_id"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany() + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Entities"); + + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.cs b/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.cs new file mode 100644 index 0000000000..477356abe1 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20220214061058_RoleBans.cs @@ -0,0 +1,82 @@ +using System; +using System.Net; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class RoleBans : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "server_role_unban", + columns: table => new + { + role_unban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ban_id = table.Column(type: "integer", nullable: false), + unbanning_admin = table.Column(type: "uuid", nullable: true), + unban_time = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id); + table.ForeignKey( + name: "FK_server_role_unban_server_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "server_role_ban", + columns: table => new + { + server_role_ban_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "uuid", nullable: true), + address = table.Column>(type: "inet", nullable: true), + hwid = table.Column(type: "bytea", nullable: true), + ban_time = table.Column(type: "timestamp with time zone", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + reason = table.Column(type: "text", nullable: false), + banning_admin = table.Column(type: "uuid", nullable: true), + unban_id = table.Column(type: "integer", nullable: true), + role_id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id); + table.CheckConstraint("CK_server_role_ban_AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + table.ForeignKey( + name: "FK_server_role_ban_server_role_unban__unban_id", + column: x => x.unban_id, + principalTable: "server_role_unban", + principalColumn: "role_unban_id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban__unban_id", + table: "server_role_ban", + column: "unban_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_unban_ban_id", + table: "server_role_unban", + column: "ban_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "server_role_ban"); + + migrationBuilder.DropTable( + name: "server_role_unban"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index bbf047634d..25266f49b1 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -646,6 +646,94 @@ namespace Content.Server.Database.Migrations.Postgres b.ToTable("server_ban_hit", (string)null); }); + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property?>("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("UnbanId") + .HasColumnType("integer") + .HasColumnName("unban_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("UnbanId") + .HasDatabaseName("IX_server_role_ban__unban_id"); + + b.ToTable("server_role_ban", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_role_unban_ban_id"); + + b.ToTable("server_role_unban", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => { b.Property("Id") @@ -842,6 +930,28 @@ namespace Content.Server.Database.Migrations.Postgres b.Navigation("Connection"); }); + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban") + .WithMany() + .HasForeignKey("UnbanId") + .HasConstraintName("FK_server_role_ban_server_role_unban__unban_id"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany() + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => { b.HasOne("Content.Server.Database.ServerBan", "Ban") diff --git a/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.Designer.cs new file mode 100644 index 0000000000..1c810a3fb7 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.Designer.cs @@ -0,0 +1,990 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20220214060745_RoleBans")] + partial class RoleBans + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("Id", "RoundId") + .HasName("PK_admin_log"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_log_round_id"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.Property("Uid") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("AdminLogId") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("AdminLogRoundId") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_round_id"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Uid") + .HasName("PK_admin_log_entity"); + + b.HasIndex("AdminLogId", "AdminLogRoundId") + .HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id"); + + b.ToTable("admin_log_entity", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("PlayerUserId", "LogId", "RoundId") + .HasName("PK_admin_log_player"); + + b.HasIndex("LogId", "RoundId"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_rank_flag_admin_rank_id"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId") + .HasDatabaseName("IX_job_profile_id"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban", (string)null); + + b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("UnbanId") + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("UnbanId") + .HasDatabaseName("IX_server_role_ban__unban_id"); + + b.ToTable("server_role_ban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_role_unban_ban_id"); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.HasOne("Content.Server.Database.AdminLog", null) + .WithMany("Entities") + .HasForeignKey("AdminLogId", "AdminLogRoundId") + .HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("LogId", "RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban") + .WithMany() + .HasForeignKey("UnbanId") + .HasConstraintName("FK_server_role_ban_server_role_unban__unban_id"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany() + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Entities"); + + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.cs b/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.cs new file mode 100644 index 0000000000..ab32fa7145 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20220214060745_RoleBans.cs @@ -0,0 +1,79 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class RoleBans : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "server_role_unban", + columns: table => new + { + role_unban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ban_id = table.Column(type: "INTEGER", nullable: false), + unbanning_admin = table.Column(type: "TEXT", nullable: true), + unban_time = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id); + table.ForeignKey( + name: "FK_server_role_unban_server_ban_ban_id", + column: x => x.ban_id, + principalTable: "server_ban", + principalColumn: "server_ban_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "server_role_ban", + columns: table => new + { + server_role_ban_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + user_id = table.Column(type: "TEXT", nullable: true), + address = table.Column(type: "TEXT", nullable: true), + hwid = table.Column(type: "BLOB", nullable: true), + ban_time = table.Column(type: "TEXT", nullable: false), + expiration_time = table.Column(type: "TEXT", nullable: true), + reason = table.Column(type: "TEXT", nullable: false), + banning_admin = table.Column(type: "TEXT", nullable: true), + unban_id = table.Column(type: "INTEGER", nullable: true), + role_id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id); + table.ForeignKey( + name: "FK_server_role_ban_server_role_unban__unban_id", + column: x => x.unban_id, + principalTable: "server_role_unban", + principalColumn: "role_unban_id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_server_role_ban__unban_id", + table: "server_role_ban", + column: "unban_id"); + + migrationBuilder.CreateIndex( + name: "IX_server_role_unban_ban_id", + table: "server_role_unban", + column: "ban_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "server_role_ban"); + + migrationBuilder.DropTable( + name: "server_role_unban"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index 89a8eebbdd..bc0d71accc 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -603,6 +603,88 @@ namespace Content.Server.Database.Migrations.Sqlite b.ToTable("server_ban_hit", (string)null); }); + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("UnbanId") + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("UnbanId") + .HasDatabaseName("IX_server_role_ban__unban_id"); + + b.ToTable("server_role_ban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_role_unban_ban_id"); + + b.ToTable("server_role_unban", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => { b.Property("Id") @@ -797,6 +879,28 @@ namespace Content.Server.Database.Migrations.Sqlite b.Navigation("Connection"); }); + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban") + .WithMany() + .HasForeignKey("UnbanId") + .HasConstraintName("FK_server_role_ban_server_role_unban__unban_id"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany() + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => { b.HasOne("Content.Server.Database.ServerBan", "Ban") diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index bd2edcbc63..1eafaf2152 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -29,6 +29,8 @@ namespace Content.Server.Database public DbSet Unban { get; set; } = default!; public DbSet ConnectionLog { get; set; } = default!; public DbSet ServerBanHit { get; set; } = default!; + public DbSet RoleBan { get; set; } = default!; + public DbSet RoleUnban { get; set; } = default!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -387,4 +389,37 @@ namespace Content.Server.Database public ServerBan Ban { get; set; } = null!; public ConnectionLog Connection { get; set; } = null!; } + + [Table("server_role_ban")] + public sealed class ServerRoleBan + { + public int Id { get; set; } + public Guid? UserId { get; set; } + [Column(TypeName = "inet")] public (IPAddress, int)? Address { get; set; } + public byte[]? HWId { get; set; } + + public DateTime BanTime { get; set; } + + public DateTime? ExpirationTime { get; set; } + + public string Reason { get; set; } = null!; + public Guid? BanningAdmin { get; set; } + + public ServerRoleUnban? Unban { get; set; } + + public string RoleId { get; set; } = null!; + } + + [Table("server_role_unban")] + public sealed class ServerRoleUnban + { + [Column("role_unban_id")] public int Id { get; set; } + + public int BanId { get; set; } + public ServerBan Ban { get; set; } = null!; + + public Guid? UnbanningAdmin { get; set; } + + public DateTime UnbanTime { get; set; } + } } diff --git a/Content.Server.Database/ModelPostgres.cs b/Content.Server.Database/ModelPostgres.cs index 682b6e5752..4c76006bd4 100644 --- a/Content.Server.Database/ModelPostgres.cs +++ b/Content.Server.Database/ModelPostgres.cs @@ -41,14 +41,15 @@ namespace Content.Server.Database { base.OnModelCreating(modelBuilder); - // ReSharper disable once CommentTypo - // ReSharper disable once StringLiteralTypo + // ReSharper disable StringLiteralTypo // Enforce that an address cannot be IPv6-mapped IPv4. // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. modelBuilder.Entity() .HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); - // ReSharper disable once StringLiteralTypo + modelBuilder.Entity() + .HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + modelBuilder.Entity() .HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); @@ -56,6 +57,7 @@ namespace Content.Server.Database modelBuilder.Entity() .HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + // ReSharper restore StringLiteralTypo foreach(var entity in modelBuilder.Model.GetEntityTypes()) { diff --git a/Content.Server.Database/ModelSqlite.cs b/Content.Server.Database/ModelSqlite.cs index 1d9a9a25ad..542b23a3c0 100644 --- a/Content.Server.Database/ModelSqlite.cs +++ b/Content.Server.Database/ModelSqlite.cs @@ -58,6 +58,12 @@ namespace Content.Server.Database .HasColumnType("TEXT") .HasConversion(ipMaskConverter); + modelBuilder + .Entity() + .Property(e => e.Address) + .HasColumnType("TEXT") + .HasConversion(ipMaskConverter); + var jsonConverter = new ValueConverter( v => JsonDocumentToString(v), v => StringToJsonDocument(v)); diff --git a/Content.Server/Administration/Commands/JobBanCommand.cs b/Content.Server/Administration/Commands/JobBanCommand.cs new file mode 100644 index 0000000000..a37ef54e4a --- /dev/null +++ b/Content.Server/Administration/Commands/JobBanCommand.cs @@ -0,0 +1,49 @@ +using Content.Server.Administration.Managers; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class JobBanCommand : IConsoleCommand +{ + public string Command => "jobban"; + public string Description => "Bans a player from a job"; + public string Help => $"Usage: {Command} [duration in minutes, leave out or 0 for permanent ban]"; + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + string target; + string job; + string reason; + uint minutes; + + switch (args.Length) + { + case 3: + target = args[0]; + job = args[1]; + reason = args[2]; + minutes = 0; + break; + case 4: + target = args[0]; + job = args[1]; + reason = args[2]; + + if (!uint.TryParse(args[3], out minutes)) + { + shell.WriteLine($"{args[3]} is not a valid amount of minutes.\n{Help}"); + return; + } + + break; + default: + shell.WriteLine($"Invalid amount of arguments."); + shell.WriteLine(Help); + return; + } + + IoCManager.Resolve().CreateJobBan(shell, target, job, reason, minutes); + } +} diff --git a/Content.Server/Administration/Commands/RoleBanListCommand.cs b/Content.Server/Administration/Commands/RoleBanListCommand.cs new file mode 100644 index 0000000000..c23c1c141e --- /dev/null +++ b/Content.Server/Administration/Commands/RoleBanListCommand.cs @@ -0,0 +1,84 @@ +using System.Text; +using Content.Server.Database; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class RoleBanListCommand : IConsoleCommand +{ + public string Command => "rolebanlist"; + public string Description => "Lists the user's role bans"; + public string Help => "Usage: [include unbanned]"; + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1 && args.Length != 2) + { + shell.WriteLine($"Invalid amount of args. {Help}"); + return; + } + + var includeUnbanned = true; + if (args.Length == 2 && !bool.TryParse(args[1], out includeUnbanned)) + { + shell.WriteLine($"Argument two ({args[1]}) is not a boolean."); + return; + } + + var dbMan = IoCManager.Resolve(); + + var target = args[0]; + + var locator = IoCManager.Resolve(); + var located = await locator.LookupIdByNameOrIdAsync(target); + if (located == null) + { + shell.WriteError("Unable to find a player with that name or id."); + return; + } + + var targetUid = located.UserId; + var targetHWid = located.LastHWId; + var targetAddress = located.LastAddress; + + var bans = await dbMan.GetServerRoleBansAsync(targetAddress, targetUid, targetHWid, includeUnbanned); + + if (bans.Count == 0) + { + shell.WriteLine("That user has no bans in their record."); + return; + } + + var bansString = new StringBuilder("Bans in record:\n"); + + foreach (var ban in bans) + { + bansString + .Append("Ban ID: ") + .Append(ban.Id) + .Append("\n") + .Append("Banned on ") + .Append(ban.BanTime); + + if (ban.ExpirationTime != null) + { + bansString + .Append(" until ") + .Append(ban.ExpirationTime.Value); + } + + bansString + .Append(".") + .Append("\n"); + + bansString + .Append("Reason: ") + .Append(ban.Reason) + .Append('\n'); + } + + shell.WriteLine(bansString.ToString()); + } +} diff --git a/Content.Server/Administration/Managers/RoleBanManager.cs b/Content.Server/Administration/Managers/RoleBanManager.cs new file mode 100644 index 0000000000..0505a1a7d5 --- /dev/null +++ b/Content.Server/Administration/Managers/RoleBanManager.cs @@ -0,0 +1,183 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Enums; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Managers; + +public sealed class RoleBanManager +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + + private const string JobPrefix = "Job:"; + + private readonly Dictionary> _cachedRoleBans = new(); + + public void Initialize() + { + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + } + + private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus != SessionStatus.Connected + || _cachedRoleBans.ContainsKey(e.Session.UserId)) + return; + + var netChannel = e.Session.ConnectedClient; + await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, netChannel.UserData.HWId); + } + + private async Task AddRoleBan(ServerRoleBanDef banDef) + { + if (banDef.UserId != null) + { + if (!_cachedRoleBans.TryGetValue(banDef.UserId.Value, out var roleBans)) + { + roleBans = new HashSet(); + _cachedRoleBans.Add(banDef.UserId.Value, roleBans); + } + if (!roleBans.Contains(banDef)) + roleBans.Add(banDef); + } + + await _db.AddServerRoleBanAsync(banDef); + return true; + } + + public HashSet? GetRoleBans(NetUserId playerUserId) + { + return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; + } + + private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) + { + var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false); + + var userRoleBans = new HashSet(); + foreach (var ban in roleBans) + { + userRoleBans.Add(ban); + } + + _cachedRoleBans[userId] = userRoleBans; + } + + public void Restart() + { + // Clear out players that have disconnected. + var toRemove = new List(); + foreach (var player in _cachedRoleBans.Keys) + { + if (!_playerManager.TryGetSessionById(player, out _)) + toRemove.Add(player); + } + + foreach (var player in toRemove) + { + _cachedRoleBans.Remove(player); + } + + // Check for expired bans + foreach (var (_, roleBans) in _cachedRoleBans) + { + roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime); + } + } + + #region Job Bans + public async void CreateJobBan(IConsoleShell shell, string target, string job, string reason, uint minutes) + { + if (!_prototypeManager.TryIndex(job, out JobPrototype? _)) + { + shell.WriteLine($"Job {job} does not exist."); + return; + } + + job = string.Concat(JobPrefix, job); + CreateRoleBan(shell, target, job, reason, minutes); + } + + public HashSet? GetJobBans(NetUserId playerUserId) + { + if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) + return null; + return roleBans + .Where(ban => ban.Role.StartsWith(JobPrefix)) + .Select(ban => ban.Role[JobPrefix.Length..]) + .ToHashSet(); + } + #endregion + + #region Commands + private async void CreateRoleBan(IConsoleShell shell, string target, string role, string reason, uint minutes) + { + var located = await _playerLocator.LookupIdByNameOrIdAsync(target); + if (located == null) + { + shell.WriteError("Unable to find a player with that name."); + return; + } + + var targetUid = located.UserId; + var targetHWid = located.LastHWId; + var targetAddress = located.LastAddress; + + DateTimeOffset? expires = null; + if (minutes > 0) + { + expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); + } + + (IPAddress, int)? addressRange = null; + if (targetAddress != null) + { + if (targetAddress.IsIPv4MappedToIPv6) + targetAddress = targetAddress.MapToIPv4(); + + // Ban /64 for IPv4, /32 for IPv4. + var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; + addressRange = (targetAddress, cidr); + } + + var player = shell.Player as IPlayerSession; + var banDef = new ServerRoleBanDef( + null, + targetUid, + addressRange, + targetHWid, + DateTimeOffset.Now, + expires, + reason, + player?.UserId, + null, + role); + + if (!await AddRoleBan(banDef)) + { + shell.WriteLine($"{target} already has a role ban for {role}"); + return; + } + + var response = new StringBuilder($"Role banned {target} with reason \"{reason}\""); + + response.Append(expires == null ? + " permanently." + : $" until {expires}"); + + shell.WriteLine(response.ToString()); + } + #endregion +} diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index df28efa951..2788a9f359 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -306,6 +306,37 @@ namespace Content.Server.Database public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban); #endregion + #region Role Bans + /* + * ROLE BANS + */ + /// + /// Looks up a role ban by id. + /// This will return a pardoned role ban as well. + /// + /// The role ban id to look for. + /// The role ban with the given id or null if none exist. + public abstract Task GetServerRoleBanAsync(int id); + + /// + /// Looks up an user's role ban history. + /// This will return pardoned role bans based on the bool. + /// Requires one of , , or to not be null. + /// + /// The IP address of the user. + /// The NetUserId of the user. + /// The Hardware Id of the user. + /// Whether expired and pardoned bans are included. + /// The user's role ban history. + public abstract Task> GetServerRoleBansAsync(IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + bool includeUnbanned); + + public abstract Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan); + public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban); + #endregion + #region Player Records /* * PLAYER RECORDS diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 03fe97f637..1d6c0b356a 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -90,6 +90,35 @@ namespace Content.Server.Database Task AddServerUnbanAsync(ServerUnbanDef serverBan); #endregion + #region Role Bans + /// + /// Looks up a role ban by id. + /// This will return a pardoned role ban as well. + /// + /// The role ban id to look for. + /// The role ban with the given id or null if none exist. + Task GetServerRoleBanAsync(int id); + + /// + /// Looks up an user's role ban history. + /// This will return pardoned role bans based on the bool. + /// Requires one of , , or to not be null. + /// + /// The IP address of the user. + /// The NetUserId of the user. + /// The Hardware Id of the user. + /// Whether expired and pardoned bans are included. + /// The user's role ban history. + Task> GetServerRoleBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + bool includeUnbanned = true); + + Task AddServerRoleBanAsync(ServerRoleBanDef serverBan); + Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan); + #endregion + #region Player Records Task UpdatePlayerRecordAsync( NetUserId userId, @@ -264,6 +293,32 @@ namespace Content.Server.Database return _db.AddServerUnbanAsync(serverUnban); } + #region Role Ban + public Task GetServerRoleBanAsync(int id) + { + return _db.GetServerRoleBanAsync(id); + } + + public Task> GetServerRoleBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + bool includeUnbanned = true) + { + return _db.GetServerRoleBansAsync(address, userId, hwId, includeUnbanned); + } + + public Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan) + { + return _db.AddServerRoleBanAsync(serverRoleBan); + } + + public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban) + { + return _db.AddServerRoleUnbanAsync(serverRoleUnban); + } + #endregion + public Task UpdatePlayerRecordAsync( NetUserId userId, string userName, diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index 82e64967e6..58d54e0b5d 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Data; using System.Linq; using System.Net; @@ -34,6 +32,7 @@ namespace Content.Server.Database }); } + #region Ban public override async Task GetServerBanAsync(int id) { await using var db = await GetDbImpl(); @@ -52,9 +51,9 @@ namespace Content.Server.Database NetUserId? userId, ImmutableArray? hwId) { - if (address == null && userId == null) + if (address == null && userId == null && hwId == null) { - throw new ArgumentException("Address and userId cannot both be null"); + throw new ArgumentException("Address, userId, and hwId cannot all be null"); } await using var db = await GetDbImpl(); @@ -73,7 +72,7 @@ namespace Content.Server.Database { if (address == null && userId == null && hwId == null) { - throw new ArgumentException("Address and userId cannot both be null"); + throw new ArgumentException("Address, userId, and hwId cannot all be null"); } await using var db = await GetDbImpl(); @@ -225,6 +224,191 @@ namespace Content.Server.Database await db.PgDbContext.SaveChangesAsync(); } + #endregion + + #region Role Ban + public override async Task GetServerRoleBanAsync(int id) + { + await using var db = await GetDbImpl(); + + var query = db.PgDbContext.RoleBan + .Include(p => p.Unban) + .Where(p => p.Id == id); + + var ban = await query.SingleOrDefaultAsync(); + + return ConvertRoleBan(ban); + + } + + public override async Task> GetServerRoleBansAsync(IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + bool includeUnbanned) + { + if (address == null && userId == null && hwId == null) + { + throw new ArgumentException("Address, userId, and hwId cannot all be null"); + } + + await using var db = await GetDbImpl(); + + var query = MakeRoleBanLookupQuery(address, userId, hwId, db, includeUnbanned) + .OrderByDescending(b => b.BanTime); + + return await QueryRoleBans(query); + } + + private static async Task> QueryRoleBans(IQueryable query) + { + var queryRoleBans = await query.ToArrayAsync(); + var bans = new List(queryRoleBans.Length); + + foreach (var ban in queryRoleBans) + { + var banDef = ConvertRoleBan(ban); + + if (banDef != null) + { + bans.Add(banDef); + } + } + + return bans; + } + + private static IQueryable MakeRoleBanLookupQuery( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + DbGuardImpl db, + bool includeUnbanned) + { + IQueryable? query = null; + + if (userId is { } uid) + { + var newQ = db.PgDbContext.RoleBan + .Include(p => p.Unban) + .Where(b => b.UserId == uid.UserId); + + query = query == null ? newQ : query.Union(newQ); + } + + if (address != null) + { + var newQ = db.PgDbContext.RoleBan + .Include(p => p.Unban) + .Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address)); + + query = query == null ? newQ : query.Union(newQ); + } + + if (hwId != null) + { + var newQ = db.PgDbContext.RoleBan + .Include(p => p.Unban) + .Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray())); + + query = query == null ? newQ : query.Union(newQ); + } + + if (!includeUnbanned) + { + query = query?.Where(p => + p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); + } + + query = query!.Distinct(); + return query; + } + + private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban) + { + if (ban == null) + { + return null; + } + + NetUserId? uid = null; + if (ban.UserId is {} guid) + { + uid = new NetUserId(guid); + } + + NetUserId? aUid = null; + if (ban.BanningAdmin is {} aGuid) + { + aUid = new NetUserId(aGuid); + } + + var unbanDef = ConvertRoleUnban(ban.Unban); + + return new ServerRoleBanDef( + ban.Id, + uid, + ban.Address, + ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.BanTime, + ban.ExpirationTime, + ban.Reason, + aUid, + unbanDef, + ban.RoleId); + } + + private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban) + { + if (unban == null) + { + return null; + } + + NetUserId? aUid = null; + if (unban.UnbanningAdmin is {} aGuid) + { + aUid = new NetUserId(aGuid); + } + + return new ServerRoleUnbanDef( + unban.Id, + aUid, + unban.UnbanTime); + } + + public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan) + { + await using var db = await GetDbImpl(); + + db.PgDbContext.RoleBan.Add(new ServerRoleBan + { + Address = serverRoleBan.Address, + HWId = serverRoleBan.HWId?.ToArray(), + Reason = serverRoleBan.Reason, + BanningAdmin = serverRoleBan.BanningAdmin?.UserId, + BanTime = serverRoleBan.BanTime.UtcDateTime, + ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime, + UserId = serverRoleBan.UserId?.UserId, + RoleId = serverRoleBan.Role, + }); + + await db.PgDbContext.SaveChangesAsync(); + } + + public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban) + { + await using var db = await GetDbImpl(); + + db.PgDbContext.RoleUnban.Add(new ServerRoleUnban + { + BanId = serverRoleUnban.BanId, + UnbanningAdmin = serverRoleUnban.UnbanningAdmin?.UserId, + UnbanTime = serverRoleUnban.UnbanTime.UtcDateTime + }); + + await db.PgDbContext.SaveChangesAsync(); + } + #endregion protected override PlayerRecord MakePlayerRecord(Player record) { diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index 4b1d1a639a..ab89379c4a 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -46,6 +46,7 @@ namespace Content.Server.Database } } + #region Ban public override async Task GetServerBanAsync(int id) { await using var db = await GetDbImpl(); @@ -159,6 +160,162 @@ namespace Content.Server.Database await db.SqliteDbContext.SaveChangesAsync(); } + #endregion + + #region Role Ban + public override async Task GetServerRoleBanAsync(int id) + { + await using var db = await GetDbImpl(); + + var ban = await db.SqliteDbContext.RoleBan + .Include(p => p.Unban) + .Where(p => p.Id == id) + .SingleOrDefaultAsync(); + + return ConvertRoleBan(ban); + } + + public override async Task> GetServerRoleBansAsync(IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + bool includeUnbanned) + { + await using var db = await GetDbImpl(); + + // SQLite can't do the net masking stuff we need to match IP address ranges. + // So just pull down the whole list into memory. + var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned); + + return queryBans + .Where(b => BanMatches(b, address, userId, hwId)) + .Select(ConvertRoleBan) + .ToList()!; + } + + private static async Task> GetAllRoleBans( + SqliteServerDbContext db, + bool includeUnbanned) + { + IQueryable query = db.RoleBan.Include(p => p.Unban); + if (!includeUnbanned) + { + query = query.Where(p => + p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)); + } + + return await query.ToListAsync(); + } + + private static bool BanMatches( + ServerRoleBan ban, + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) + { + if (address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value)) + { + return true; + } + + if (userId is { } id && ban.UserId == id.UserId) + { + return true; + } + + if (hwId is { } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId)) + { + return true; + } + + return false; + } + + public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverBan) + { + await using var db = await GetDbImpl(); + + db.SqliteDbContext.RoleBan.Add(new ServerRoleBan + { + Address = serverBan.Address, + Reason = serverBan.Reason, + BanningAdmin = serverBan.BanningAdmin?.UserId, + HWId = serverBan.HWId?.ToArray(), + BanTime = serverBan.BanTime.UtcDateTime, + ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, + UserId = serverBan.UserId?.UserId, + RoleId = serverBan.Role, + }); + + await db.SqliteDbContext.SaveChangesAsync(); + } + + public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnban) + { + await using var db = await GetDbImpl(); + + db.SqliteDbContext.RoleUnban.Add(new ServerRoleUnban + { + BanId = serverUnban.BanId, + UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId, + UnbanTime = serverUnban.UnbanTime.UtcDateTime + }); + + await db.SqliteDbContext.SaveChangesAsync(); + } + + private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban) + { + if (ban == null) + { + return null; + } + + NetUserId? uid = null; + if (ban.UserId is { } guid) + { + uid = new NetUserId(guid); + } + + NetUserId? aUid = null; + if (ban.BanningAdmin is { } aGuid) + { + aUid = new NetUserId(aGuid); + } + + var unban = ConvertRoleUnban(ban.Unban); + + return new ServerRoleBanDef( + ban.Id, + uid, + ban.Address, + ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), + ban.BanTime, + ban.ExpirationTime, + ban.Reason, + aUid, + unban, + ban.RoleId); + } + + private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban) + { + if (unban == null) + { + return null; + } + + NetUserId? aUid = null; + if (unban.UnbanningAdmin is { } aGuid) + { + aUid = new NetUserId(aGuid); + } + + return new ServerRoleUnbanDef( + unban.Id, + aUid, + unban.UnbanTime); + } + #endregion protected override PlayerRecord MakePlayerRecord(Player record) { diff --git a/Content.Server/Database/ServerRoleBanDef.cs b/Content.Server/Database/ServerRoleBanDef.cs new file mode 100644 index 0000000000..5e6da3115f --- /dev/null +++ b/Content.Server/Database/ServerRoleBanDef.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using System.Net; +using Robust.Shared.Network; + +namespace Content.Server.Database; + +public sealed class ServerRoleBanDef +{ + public int? Id { get; } + public NetUserId? UserId { get; } + public (IPAddress address, int cidrMask)? Address { get; } + public ImmutableArray? HWId { get; } + + public DateTimeOffset BanTime { get; } + public DateTimeOffset? ExpirationTime { get; } + public string Reason { get; } + public NetUserId? BanningAdmin { get; } + public ServerRoleUnbanDef? Unban { get; } + public string Role { get; } + + public ServerRoleBanDef( + int? id, + NetUserId? userId, + (IPAddress, int)? address, + ImmutableArray? hwId, + DateTimeOffset banTime, + DateTimeOffset? expirationTime, + string reason, + NetUserId? banningAdmin, + ServerRoleUnbanDef? unban, + string role) + { + if (userId == null && address == null && hwId == null) + { + throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID"); + } + + if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6) + { + // Fix IPv6-mapped IPv4 addresses + // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. + address = (addr.Item1.MapToIPv4(), addr.Item2 - 96); + } + + Id = id; + UserId = userId; + Address = address; + HWId = hwId; + BanTime = banTime; + ExpirationTime = expirationTime; + Reason = reason; + BanningAdmin = banningAdmin; + Unban = unban; + Role = role; + } +} diff --git a/Content.Server/Database/ServerRoleUnbanDef.cs b/Content.Server/Database/ServerRoleUnbanDef.cs new file mode 100644 index 0000000000..3960a86808 --- /dev/null +++ b/Content.Server/Database/ServerRoleUnbanDef.cs @@ -0,0 +1,19 @@ +using Robust.Shared.Network; + +namespace Content.Server.Database; + +public sealed class ServerRoleUnbanDef +{ + public int BanId { get; } + + public NetUserId? UnbanningAdmin { get; } + + public DateTimeOffset UnbanTime { get; } + + public ServerRoleUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime) + { + BanId = banId; + UnbanningAdmin = unbanningAdmin; + UnbanTime = unbanTime; + } +} diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 716a9870c3..a77228dde6 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -126,6 +126,7 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().GetEntitySystem().PostInitialize(); IoCManager.Resolve().DoAutoRegistrations(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index 4087f9afb5..a69b75535e 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -7,6 +7,7 @@ using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.MobState.Components; +using Robust.Server.Player; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -18,6 +19,56 @@ namespace Content.Server.GameTicking private GamePresetPrototype? _preset; + private bool StartPreset(IPlayerSession[] origReadyPlayers, bool force) + { + var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force); + RaiseLocalEvent(startAttempt); + + if (!startAttempt.Cancelled) + return true; + + var presetTitle = _preset != null ? Loc.GetString(_preset.ModeTitle) : string.Empty; + + void FailedPresetRestart() + { + SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart", + ("failedGameMode", presetTitle))); + RestartRound(); + DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease)); + } + + if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled)) + { + var oldPreset = _preset; + ClearGameRules(); + SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset)); + AddGamePresetRules(); + StartGamePresetRules(); + + startAttempt.Uncancel(); + RaiseLocalEvent(startAttempt); + + _chatManager.DispatchServerAnnouncement( + Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback", + ("failedGameMode", presetTitle), + ("fallbackMode", Loc.GetString(_preset!.ModeTitle)))); + + if (startAttempt.Cancelled) + { + FailedPresetRestart(); + } + + RefreshLateJoinAllowed(); + } + else + { + FailedPresetRestart(); + return false; + } + + return true; + } + private void InitializeGamePreset() { SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset)); diff --git a/Content.Server/GameTicking/GameTicker.JobController.cs b/Content.Server/GameTicking/GameTicker.JobController.cs index 45e5c95070..e35548a86d 100644 --- a/Content.Server/GameTicking/GameTicker.JobController.cs +++ b/Content.Server/GameTicking/GameTicker.JobController.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -7,12 +6,10 @@ using Content.Shared.Preferences; using Content.Shared.Roles; using Content.Shared.Station; using Robust.Server.Player; -using Robust.Shared.Localization; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; namespace Content.Server.GameTicking { @@ -25,17 +22,18 @@ namespace Content.Server.GameTicking [ViewVariables] private readonly Dictionary _spawnedPositions = new(); - private Dictionary AssignJobs(List available, + private Dictionary AssignJobs(List availablePlayers, Dictionary profiles) { var assigned = new Dictionary(); List<(IPlayerSession, List)> GetPlayersJobCandidates(bool heads, JobPriority i) { - return available.Select(player => + return availablePlayers.Select(player => { var profile = profiles[player.UserId]; + var roleBans = _roleBanManager.GetJobBans(player.UserId); var availableJobs = profile.JobPriorities .Where(j => { @@ -53,6 +51,7 @@ namespace Content.Server.GameTicking return priority == i; }) + .Where(p => roleBans != null && !roleBans.Contains(p.Key)) .Select(j => j.Key) .ToList(); @@ -85,7 +84,7 @@ namespace Content.Server.GameTicking } } - available.RemoveAll(a => assigned.ContainsKey(a)); + availablePlayers.RemoveAll(a => assigned.ContainsKey(a)); } // Current strategy is to fill each station one by one. @@ -106,14 +105,17 @@ namespace Content.Server.GameTicking return assigned; } - private string? PickBestAvailableJob(HumanoidCharacterProfile profile, StationId station) + private string? PickBestAvailableJob(IPlayerSession playerSession, HumanoidCharacterProfile profile, + StationId station) { var available = _stationSystem.StationInfo[station].JobList; bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId) { + var roleBans = _roleBanManager.GetJobBans(playerSession.UserId); var filtered = profile.JobPriorities .Where(p => p.Value == priority) + .Where(p => roleBans != null && !roleBans.Contains(p.Key)) .Select(p => p.Key) .ToList(); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 8a626806b7..3a782e5994 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -69,7 +69,7 @@ namespace Content.Server.GameTicking [ViewVariables] public int RoundId { get; private set; } - private void PreRoundSetup() + private void LoadMaps() { AddGamePresetRules(); @@ -192,6 +192,14 @@ namespace Content.Server.GameTicking StartGamePresetRules(); + RoundLengthMetric.Set(0); + + var playerIds = _playersInLobby.Keys.Select(player => player.UserId.UserId).ToArray(); + RoundId = await _db.AddNewRound(playerIds); + + var startingEvent = new RoundStartingEvent(); + RaiseLocalEvent(startingEvent); + List readyPlayers; if (LobbyEnabled) { @@ -203,13 +211,13 @@ namespace Content.Server.GameTicking readyPlayers = _playersInLobby.Keys.ToList(); } - RoundLengthMetric.Set(0); - - var playerIds = _playersInLobby.Keys.Select(player => player.UserId.UserId).ToArray(); - RoundId = await _db.AddNewRound(playerIds); - - var startingEvent = new RoundStartingEvent(); - RaiseLocalEvent(startingEvent); + readyPlayers.RemoveAll(p => + { + if (_roleBanManager.GetRoleBans(p.UserId) != null) + return false; + Logger.ErrorS("RoleBans", $"Role bans for player {p} {p.UserId} have not been loaded yet."); + return true; + }); // Get the profiles for each player for easier lookup. var profiles = _prefsManager.GetSelectedProfilesForPlayers( @@ -227,106 +235,13 @@ namespace Content.Server.GameTicking var origReadyPlayers = readyPlayers.ToArray(); - var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force); - RaiseLocalEvent(startAttempt); - - var presetTitle = _preset != null ? Loc.GetString(_preset.ModeTitle) : string.Empty; - - void FailedPresetRestart() - { - SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart", - ("failedGameMode", presetTitle))); - RestartRound(); - DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease)); - } - - if (startAttempt.Cancelled) - { - if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled)) - { - var oldPreset = _preset; - ClearGameRules(); - SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset)); - AddGamePresetRules(); - StartGamePresetRules(); - - startAttempt.Uncancel(); - RaiseLocalEvent(startAttempt); - - _chatManager.DispatchServerAnnouncement( - Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback", - ("failedGameMode", presetTitle), - ("fallbackMode", Loc.GetString(_preset!.ModeTitle)))); - - if (startAttempt.Cancelled) - { - FailedPresetRestart(); - } - - RefreshLateJoinAllowed(); - } - else - { - FailedPresetRestart(); - return; - } - } + if (!StartPreset(origReadyPlayers, force)) + return; // MapInitialize *before* spawning players, our codebase is too shit to do it afterwards... _mapManager.DoMapInitialize(DefaultMap); - // Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard) - RaiseLocalEvent(new RulePlayerSpawningEvent(readyPlayers, profiles, force)); - - var assignedJobs = AssignJobs(readyPlayers, profiles); - - // For players without jobs, give them the overflow job if they have that set... - foreach (var player in origReadyPlayers) - { - if (assignedJobs.ContainsKey(player)) - { - continue; - } - - var profile = profiles[player.UserId]; - if (profile.PreferenceUnavailable == PreferenceUnavailableMode.SpawnAsOverflow) - { - // Pick a random station - var stations = _stationSystem.StationInfo.Keys.ToList(); - _robustRandom.Shuffle(stations); - - if (stations.Count == 0) - { - assignedJobs.Add(player, (FallbackOverflowJob, StationId.Invalid)); - continue; - } - - foreach (var station in stations) - { - // Pick a random overflow job from that station - var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone(); - _robustRandom.Shuffle(overflows); - - // Stations with no overflow slots should simply get skipped over. - if (overflows.Count == 0) - continue; - - // If the overflow exists, put them in as it. - assignedJobs.Add(player, (overflows[0], stations[0])); - } - } - } - - // Spawn everybody in! - foreach (var (player, (job, station)) in assignedJobs) - { - SpawnPlayer(player, profiles[player.UserId], station, job, false); - } - - RefreshLateJoinAllowed(); - - // Allow rules to add roles to players who have been spawned in. (For example, on-station traitors) - RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.ToArray(), profiles, force)); + SpawnPlayers(readyPlayers, origReadyPlayers, profiles, force); _roundStartDateTime = DateTime.UtcNow; RunLevel = GameRunLevel.InRound; @@ -458,7 +373,7 @@ namespace Content.Server.GameTicking RunLevel = GameRunLevel.PreRoundLobby; LobbySong = _robustRandom.Pick(_lobbyMusicCollection.PickFiles).ToString(); ResettingCleanup(); - PreRoundSetup(); + LoadMaps(); if (!LobbyEnabled) { @@ -508,6 +423,8 @@ namespace Content.Server.GameTicking _mapManager.Restart(); + _roleBanManager.Restart(); + // Clear up any game rules. ClearGameRules(); diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 3100c67b71..31a7571b7c 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Globalization; using System.Linq; using Content.Server.Access.Systems; @@ -22,13 +20,10 @@ using Content.Shared.Roles; using Content.Shared.Species; using Content.Shared.Station; using Robust.Server.Player; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; namespace Content.Server.GameTicking { @@ -48,6 +43,71 @@ namespace Content.Server.GameTicking // Mainly to avoid allocations. private readonly List _possiblePositions = new(); + private void SpawnPlayers(List readyPlayers, IPlayerSession[] origReadyPlayers, + Dictionary profiles, bool force) + { + // Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard) + RaiseLocalEvent(new RulePlayerSpawningEvent(readyPlayers, profiles, force)); + + var assignedJobs = AssignJobs(readyPlayers, profiles); + + AssignOverflowJobs(assignedJobs, origReadyPlayers, profiles); + + // Spawn everybody in! + foreach (var (player, (job, station)) in assignedJobs) + { + SpawnPlayer(player, profiles[player.UserId], station, job, false); + } + + RefreshLateJoinAllowed(); + + // Allow rules to add roles to players who have been spawned in. (For example, on-station traitors) + RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.ToArray(), profiles, force)); + } + + private void AssignOverflowJobs(IDictionary assignedJobs, + IPlayerSession[] origReadyPlayers, IReadOnlyDictionary profiles) + { + // For players without jobs, give them the overflow job if they have that set... + foreach (var player in origReadyPlayers) + { + if (assignedJobs.ContainsKey(player)) + { + continue; + } + + var profile = profiles[player.UserId]; + if (profile.PreferenceUnavailable != PreferenceUnavailableMode.SpawnAsOverflow) + continue; + + // Pick a random station + var stations = _stationSystem.StationInfo.Keys.ToList(); + + if (stations.Count == 0) + { + assignedJobs.Add(player, (FallbackOverflowJob, StationId.Invalid)); + continue; + } + + _robustRandom.Shuffle(stations); + + foreach (var station in stations) + { + // Pick a random overflow job from that station + var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone(); + _robustRandom.Shuffle(overflows); + + // Stations with no overflow slots should simply get skipped over. + if (overflows.Count == 0) + continue; + + // If the overflow exists, put them in as it. + assignedJobs.Add(player, (overflows[0], stations[0])); + break; + } + } + } + private void SpawnPlayer(IPlayerSession player, StationId station, string? jobId = null, bool lateJoin = true) { var character = GetPlayerProfile(player); @@ -90,7 +150,7 @@ namespace Content.Server.GameTicking } // Pick best job best on prefs. - jobId ??= PickBestAvailableJob(character, station); + jobId ??= PickBestAvailableJob(player, character, station); // If no job available, stay in lobby, or if no lobby spawn as observer if (jobId is null) { diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 13f573f1a9..e3b1ef5518 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -1,4 +1,5 @@ using Content.Server.Administration.Logs; +using Content.Server.Administration.Managers; using Content.Server.CharacterAppearance.Systems; using Content.Server.Chat.Managers; using Content.Server.Ghost; @@ -16,14 +17,12 @@ using Robust.Shared.Configuration; #if EXCEPTION_TOLERANCE using Robust.Shared.Exceptions; #endif -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; namespace Content.Server.GameTicking { @@ -99,5 +98,6 @@ namespace Content.Server.GameTicking [Dependency] private readonly PDASystem _pdaSystem = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly GhostSystem _ghosts = default!; + [Dependency] private readonly RoleBanManager _roleBanManager = default!; } } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 3a549ebd26..ccc36ef576 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -53,6 +53,7 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } }