diff --git a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs index b4975f7d5e..b48c9afcc7 100644 --- a/Content.IntegrationTests/Tests/Commands/PardonCommand.cs +++ b/Content.IntegrationTests/Tests/Commands/PardonCommand.cs @@ -31,17 +31,17 @@ namespace Content.IntegrationTests.Tests.Commands var clientId = clientSession.UserId; // No bans on record - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Is.Empty); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); // Try to pardon a ban that does not exist sConsole.ExecuteCommand("pardon 1"); // Still no bans on record - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Is.Empty); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Is.Empty); var banReason = "test"; @@ -49,20 +49,20 @@ namespace Content.IntegrationTests.Tests.Commands sConsole.ExecuteCommand($"ban {clientSession.Name} {banReason} 1440"); // Should have one ban on record now - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Not.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); // Try to pardon a ban that does not exist sConsole.ExecuteCommand("pardon 2"); // The existing ban is unaffected - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Not.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Not.Null); var ban = await sDatabase.GetServerBanAsync(1); Assert.That(ban, Is.Not.Null); - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); // Check that it matches Assert.That(ban.Id, Is.EqualTo(1)); @@ -81,14 +81,14 @@ namespace Content.IntegrationTests.Tests.Commands sConsole.ExecuteCommand("pardon 1"); // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); // Direct id lookup returns a pardoned ban var pardonedBan = await sDatabase.GetServerBanAsync(1); Assert.That(pardonedBan, Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); // Check that it matches Assert.That(pardonedBan.Id, Is.EqualTo(1)); @@ -114,13 +114,13 @@ namespace Content.IntegrationTests.Tests.Commands // Nothing changes // No bans should be returned - Assert.That(await sDatabase.GetServerBanAsync(null, clientId), Is.Null); + Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null), Is.Null); // Direct id lookup returns a pardoned ban Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); // The list is still returned since that ignores pardons - Assert.That(await sDatabase.GetServerBansAsync(null, clientId), Has.Count.EqualTo(1)); + Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null), Has.Count.EqualTo(1)); }); } } diff --git a/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.Designer.cs b/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.Designer.cs new file mode 100644 index 0000000000..ce4962fb34 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.Designer.cs @@ -0,0 +1,592 @@ +// +using System; +using System.Net; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20210321230012_HWID")] + partial class HWID + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("AdminId"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("admin_rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + 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"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log"); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player"); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban"); + + 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.PostgresServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + 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.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + + 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(); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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(); + + 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(); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.HasOne("Content.Server.Database.PostgresServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.cs b/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.cs new file mode 100644 index 0000000000..172c973c47 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20210321230012_HWID.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class HWID : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "HaveEitherAddressOrUserId", + table: "server_ban"); + + migrationBuilder.AddColumn( + name: "hwid", + table: "server_ban", + type: "bytea", + nullable: true); + + migrationBuilder.AddColumn( + name: "last_seen_hwid", + table: "player", + type: "bytea", + nullable: true); + + migrationBuilder.AddColumn( + name: "hwid", + table: "connection_log", + type: "bytea", + nullable: true); + + migrationBuilder.AddCheckConstraint( + name: "HaveEitherAddressOrUserIdOrHWId", + table: "server_ban", + sql: "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "HaveEitherAddressOrUserIdOrHWId", + table: "server_ban"); + + migrationBuilder.DropColumn( + name: "hwid", + table: "server_ban"); + + migrationBuilder.DropColumn( + name: "last_seen_hwid", + table: "player"); + + migrationBuilder.DropColumn( + name: "hwid", + table: "connection_log"); + + migrationBuilder.AddCheckConstraint( + name: "HaveEitherAddressOrUserId", + table: "server_ban", + sql: "address IS NOT NULL OR user_id IS NOT NULL"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index cb42d609c3..f849591596 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -16,9 +16,9 @@ namespace Content.Server.Database.Migrations.Postgres { #pragma warning disable 612, 618 modelBuilder - .UseIdentityByDefaultColumns() .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.0"); + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("Content.Server.Database.Admin", b => { @@ -48,7 +48,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("admin_flag_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("AdminId") .HasColumnType("uuid") @@ -79,7 +79,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("admin_rank_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("Name") .IsRequired() @@ -97,7 +97,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("admin_rank_flag_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("AdminRankId") .HasColumnType("integer") @@ -124,7 +124,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("antag_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("AntagName") .IsRequired() @@ -149,7 +149,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("assigned_user_id_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("UserId") .HasColumnType("uuid") @@ -177,7 +177,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("job_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("JobName") .IsRequired() @@ -205,13 +205,17 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("connection_log_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("Address") .IsRequired() .HasColumnType("inet") .HasColumnName("address"); + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + b.Property("Time") .HasColumnType("timestamp with time zone") .HasColumnName("time"); @@ -240,7 +244,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("player_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("FirstSeenTime") .HasColumnType("timestamp with time zone") @@ -251,6 +255,10 @@ namespace Content.Server.Database.Migrations.Postgres .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"); @@ -282,7 +290,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("server_ban_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property?>("Address") .HasColumnType("inet") @@ -300,6 +308,10 @@ namespace Content.Server.Database.Migrations.Postgres .HasColumnType("timestamp with time zone") .HasColumnName("expiration_time"); + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + b.Property("Reason") .IsRequired() .HasColumnType("text") @@ -319,7 +331,7 @@ namespace Content.Server.Database.Migrations.Postgres b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); - b.HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); }); modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => @@ -328,7 +340,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("unban_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("BanId") .HasColumnType("integer") @@ -356,7 +368,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("preference_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("AdminOOCColor") .IsRequired() @@ -385,7 +397,7 @@ namespace Content.Server.Database.Migrations.Postgres .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("profile_id") - .UseIdentityByDefaultColumn(); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); b.Property("Age") .HasColumnType("integer") diff --git a/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.Designer.cs new file mode 100644 index 0000000000..6c18ec1405 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.Designer.cs @@ -0,0 +1,559 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20210321225959_HWID")] + partial class HWID + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + 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"); + + b.HasIndex("AdminRankId"); + + b.ToTable("admin"); + }); + + 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"); + + b.HasIndex("AdminId"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag"); + }); + + 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"); + + b.ToTable("admin_rank"); + }); + + 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"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag"); + }); + + 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"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag"); + }); + + 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"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id"); + }); + + 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"); + + b.HasIndex("ProfileId"); + + b.ToTable("job"); + }); + + 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"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference"); + }); + + 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.HasKey("Id"); + + b.HasIndex("PreferenceId"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + 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"); + + b.ToTable("connection_log"); + }); + + modelBuilder.Entity("Content.Server.Database.SqlitePlayer", 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"); + + b.HasIndex("LastSeenUserName"); + + b.ToTable("player"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("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"); + + b.ToTable("ban"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", 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"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull); + + 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(); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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(); + + 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(); + + 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(); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.HasOne("Content.Server.Database.SqliteServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + 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.SqliteServerBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.cs b/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.cs new file mode 100644 index 0000000000..5c27c5dca2 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20210321225959_HWID.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class HWID : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "last_seen_hwid", + table: "player", + type: "BLOB", + nullable: true); + + migrationBuilder.AddColumn( + name: "hwid", + table: "connection_log", + type: "BLOB", + nullable: true); + + migrationBuilder.AddColumn( + name: "hwid", + table: "ban", + type: "BLOB", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "last_seen_hwid", + table: "player"); + + migrationBuilder.DropColumn( + name: "hwid", + table: "connection_log"); + + migrationBuilder.DropColumn( + name: "hwid", + table: "ban"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index b4fca5d3e2..45d6ff7405 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace Content.Server.Database.Migrations.Sqlite { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.0"); + .HasAnnotation("ProductVersion", "5.0.3"); modelBuilder.Entity("Content.Server.Database.Admin", b => { @@ -317,6 +317,10 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("address"); + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + b.Property("Time") .HasColumnType("TEXT") .HasColumnName("time"); @@ -351,6 +355,10 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("last_seen_address"); + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + b.Property("LastSeenTime") .HasColumnType("TEXT") .HasColumnName("last_seen_time"); @@ -394,6 +402,10 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("TEXT") .HasColumnName("expiration_time"); + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + b.Property("Reason") .IsRequired() .HasColumnType("TEXT") diff --git a/Content.Server.Database/ModelPostgres.cs b/Content.Server.Database/ModelPostgres.cs index 400768227c..f36536fa49 100644 --- a/Content.Server.Database/ModelPostgres.cs +++ b/Content.Server.Database/ModelPostgres.cs @@ -54,7 +54,7 @@ namespace Content.Server.Database // 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") - .HasCheckConstraint("HaveEitherAddressOrUserId", "address IS NOT NULL OR user_id IS NOT NULL"); + .HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); modelBuilder.Entity() .HasIndex(p => p.UserId) @@ -84,6 +84,7 @@ namespace Content.Server.Database [Column("user_id")] public Guid? UserId { get; set; } [Column("address", TypeName = "inet")] public (IPAddress, int)? Address { get; set; } + [Column("hwid")] public byte[]? HWId { get; set; } [Column("ban_time", TypeName = "timestamp with time zone")] public DateTime BanTime { get; set; } @@ -129,6 +130,7 @@ namespace Content.Server.Database public DateTime LastSeenTime { get; set; } [Column("last_seen_address")] public IPAddress LastSeenAddress { get; set; } = null!; + [Column("last_seen_hwid")] public byte[]? LastSeenHWId { get; set; } } [Table("connection_log")] @@ -143,5 +145,6 @@ namespace Content.Server.Database public DateTime Time { get; set; } [Column("address")] public IPAddress Address { get; set; } = null!; + [Column("hwid")] public byte[]? HWId { get; set; } } } diff --git a/Content.Server.Database/ModelSqlite.cs b/Content.Server.Database/ModelSqlite.cs index 4032bb2b28..e82c16cf72 100644 --- a/Content.Server.Database/ModelSqlite.cs +++ b/Content.Server.Database/ModelSqlite.cs @@ -41,6 +41,7 @@ namespace Content.Server.Database [Column("user_id")] public Guid? UserId { get; set; } [Column("address")] public string? Address { get; set; } + [Column("hwid")] public byte[]? HWId { get; set; } [Column("ban_time")] public DateTime BanTime { get; set; } [Column("expiration_time")] public DateTime? ExpirationTime { get; set; } @@ -75,6 +76,7 @@ namespace Content.Server.Database [Column("last_seen_user_name")] public string LastSeenUserName { get; set; } = null!; [Column("last_seen_time")] public DateTime LastSeenTime { get; set; } [Column("last_seen_address")] public string LastSeenAddress { get; set; } = null!; + [Column("last_seen_hwid")] public byte[]? LastSeenHWId { get; set; } } [Table("connection_log")] @@ -86,5 +88,6 @@ namespace Content.Server.Database [Column("user_name")] public string UserName { get; set; } = null!; [Column("time")] public DateTime Time { get; set; } [Column("address")] public string Address { get; set; } = null!; + [Column("hwid")] public byte[]? HWId { get; set; } } } diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index fd80bc99fe..64ebd4807f 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -1,4 +1,6 @@ using System; +using System.Net; +using System.Net.Sockets; using System.Text; using Content.Server.Database; using Content.Shared.Administration; @@ -51,14 +53,16 @@ namespace Content.Server.Administration.Commands return; } - var resolvedUid = await locator.LookupIdByNameOrIdAsync(target); - if (resolvedUid == null) + var located = await locator.LookupIdByNameOrIdAsync(target); + if (located == null) { shell.WriteError("Unable to find a player with that name."); return; } - var targetUid = resolvedUid.Value; + var targetUid = located.UserId; + var targetHWid = located.LastHWId; + var targetAddr = located.LastAddress; if (player != null && player.UserId == targetUid) { @@ -72,7 +76,29 @@ namespace Content.Server.Administration.Commands expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); } - await dbMan.AddServerBanAsync(new ServerBanDef(null, targetUid, null, DateTimeOffset.Now, expires, reason, player?.UserId, null)); + (IPAddress, int)? addrRange = null; + if (targetAddr != null) + { + if (targetAddr.IsIPv4MappedToIPv6) + targetAddr = targetAddr.MapToIPv4(); + + // Ban /64 for IPv4, /32 for IPv4. + var cidr = targetAddr.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; + addrRange = (targetAddr, cidr); + } + + var banDef = new ServerBanDef( + null, + targetUid, + addrRange, + targetHWid, + DateTimeOffset.Now, + expires, + reason, + player?.UserId, + null); + + await dbMan.AddServerBanAsync(banDef); var response = new StringBuilder($"Banned {target} with reason \"{reason}\""); diff --git a/Content.Server/Administration/Commands/BanListCommand.cs b/Content.Server/Administration/Commands/BanListCommand.cs index 368df2224e..3bb9894ecb 100644 --- a/Content.Server/Administration/Commands/BanListCommand.cs +++ b/Content.Server/Administration/Commands/BanListCommand.cs @@ -44,7 +44,7 @@ namespace Content.Server.Administration.Commands return; } - var bans = await dbMan.GetServerBansAsync(null, targetUid); + var bans = await dbMan.GetServerBansAsync(null, targetUid, null); if (bans.Count == 0) { diff --git a/Content.Server/Administration/PlayerLocator.cs b/Content.Server/Administration/PlayerLocator.cs index 84aa23483a..9f180548c3 100644 --- a/Content.Server/Administration/PlayerLocator.cs +++ b/Content.Server/Administration/PlayerLocator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Net; using System.Net.Http; using System.Net.Http.Json; @@ -15,6 +16,8 @@ using Robust.Shared.Network; namespace Content.Server.Administration { + public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray? LastHWId); + /// /// Utilities for finding user IDs that extend to more than the server database. /// @@ -28,19 +31,13 @@ namespace Content.Server.Administration /// Look up a user ID by name globally. /// /// Null if the player does not exist. - Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default); + Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default); /// - /// If passed a GUID, runs and only returns it if the account exists. + /// If passed a GUID, looks up the ID and tries to find HWId for it. /// If passed a player name, returns . /// - Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default); - - /// - /// Checks whether the specified user ID is an existing account, globally. - /// - /// True if the player account exists, false otherwise - Task DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default); + Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default); } internal sealed class PlayerLocator : IPlayerLocator @@ -49,22 +46,27 @@ namespace Content.Server.Administration [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IServerDbManager _db = default!; - public async Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default) + public async Task LookupIdByNameAsync(string playerName, CancellationToken cancel = default) { // Check people currently on the server, the easiest case. if (_playerManager.TryGetSessionByUsername(playerName, out var session)) - return session.UserId; + { + var userId = session.UserId; + var address = session.ConnectedClient.RemoteEndPoint.Address; + var hwId = session.ConnectedClient.UserData.HWId; + return new LocatedPlayerData(userId, address, hwId); + } // Check database for past players. var record = await _db.GetPlayerRecordByUserName(playerName, cancel); if (record != null) - return record.UserId; + return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId); // If all else fails, ask the auth server. var client = new HttpClient(); var authServer = _configurationManager.GetCVar(CVars.AuthServer); - var resp = await client.GetAsync($"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}", - cancel); + var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}"; + using var resp = await client.GetAsync(requestUri, cancel); if (resp.StatusCode == HttpStatusCode.NotFound) return null; @@ -83,45 +85,49 @@ namespace Content.Server.Administration return null; } - return new NetUserId(responseData.UserId); + return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null); } - public async Task DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default) + public async Task LookupIdAsync(NetUserId userId, CancellationToken cancel = default) { // Check people currently on the server, the easiest case. - if (_playerManager.ValidSessionId(userId)) - return true; + if (_playerManager.TryGetSessionById(userId, out var session)) + { + var address = session.ConnectedClient.RemoteEndPoint.Address; + var hwId = session.ConnectedClient.UserData.HWId; + return new LocatedPlayerData(userId, address, hwId); + } // Check database for past players. var record = await _db.GetPlayerRecordByUserId(userId, cancel); if (record != null) - return true; + return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId); // If all else fails, ask the auth server. var client = new HttpClient(); var authServer = _configurationManager.GetCVar(CVars.AuthServer); var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}"; - var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, requestUri), cancel); + using var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, requestUri), cancel); if (resp.StatusCode == HttpStatusCode.NotFound) - return false; + return null; if (!resp.IsSuccessStatusCode) { Logger.ErrorS("PlayerLocate", "Auth server returned bad response {StatusCode}!", resp.StatusCode); - return false; + return null; } - return true; + return new LocatedPlayerData(userId, null, null); } - public async Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) + public async Task LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) { if (Guid.TryParse(playerName, out var guid)) { var userId = new NetUserId(guid); - return await DoesPlayerExistAsync(userId, cancel) ? userId : null; + return await LookupIdAsync(userId, cancel); } return await LookupIdByNameAsync(playerName, cancel); diff --git a/Content.Server/ConnectionManager.cs b/Content.Server/ConnectionManager.cs index e437e57482..eea230262f 100644 --- a/Content.Server/ConnectionManager.cs +++ b/Content.Server/ConnectionManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Threading.Tasks; using Content.Server.Database; using Content.Server.Preferences; @@ -61,7 +62,14 @@ The ban reason is: ""{ban.Reason}"" // Check if banned. var addr = e.IP.Address; var userId = e.UserId; - var ban = await _db.GetServerBanAsync(addr, userId); + ImmutableArray? hwId = e.UserData.HWId; + if (hwId.Value.Length == 0) + { + // HWId not available for user's platform, don't look it up. + hwId = null; + } + + var ban = await _db.GetServerBanAsync(addr, userId, hwId); if (ban != null) { var expires = "This is a permanent ban."; @@ -83,8 +91,8 @@ The ban reason is: ""{ban.Reason}"" return; } - await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr); - await _db.AddConnectionLogAsync(userId, e.UserName, addr); + await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, e.UserData.HWId); + await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId); } private async Task AssignUserIdCallback(string name) diff --git a/Content.Server/Database/PlayerRecord.cs b/Content.Server/Database/PlayerRecord.cs index 19e05f833c..f5931182ef 100644 --- a/Content.Server/Database/PlayerRecord.cs +++ b/Content.Server/Database/PlayerRecord.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Net; using Robust.Shared.Network; @@ -7,6 +8,7 @@ namespace Content.Server.Database public sealed class PlayerRecord { public NetUserId UserId { get; } + public ImmutableArray? HWId { get; } public DateTimeOffset FirstSeenTime { get; } public string LastSeenUserName { get; } public DateTimeOffset LastSeenTime { get; } @@ -17,13 +19,15 @@ namespace Content.Server.Database DateTimeOffset firstSeenTime, string lastSeenUserName, DateTimeOffset lastSeenTime, - IPAddress lastSeenAddress) + IPAddress lastSeenAddress, + ImmutableArray? hwId) { UserId = userId; FirstSeenTime = firstSeenTime; LastSeenUserName = lastSeenUserName; LastSeenTime = lastSeenTime; LastSeenAddress = lastSeenAddress; + HWId = hwId; } } } diff --git a/Content.Server/Database/ServerBanDef.cs b/Content.Server/Database/ServerBanDef.cs index 302a0c3d70..c1c1b32f8c 100644 --- a/Content.Server/Database/ServerBanDef.cs +++ b/Content.Server/Database/ServerBanDef.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Immutable; using System.Net; using Robust.Shared.Network; @@ -11,6 +12,7 @@ namespace Content.Server.Database 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; } @@ -22,15 +24,16 @@ namespace Content.Server.Database int? id, NetUserId? userId, (IPAddress, int)? address, + ImmutableArray? hwId, DateTimeOffset banTime, DateTimeOffset? expirationTime, string reason, NetUserId? banningAdmin, ServerUnbanDef? unban) { - if (userId == null && address == null) + if (userId == null && address == null && hwId == null) { - throw new ArgumentException("Must have a banned user, banned address, or both."); + throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID"); } if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6) @@ -43,6 +46,7 @@ namespace Content.Server.Database Id = id; UserId = userId; Address = address; + HWId = hwId; BanTime = banTime; ExpirationTime = expirationTime; Reason = reason; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index d6af1d533a..3e4c22b1e3 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net; using System.Threading; @@ -261,8 +262,12 @@ namespace Content.Server.Database /// /// The ip address of the user. /// The id of the user. + /// The HWId of the user. /// The user's latest received un-pardoned ban, or null if none exist. - public abstract Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + public abstract Task GetServerBanAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId); /// /// Looks up an user's ban history. @@ -271,8 +276,12 @@ namespace Content.Server.Database /// /// The ip address of the user. /// The id of the user. + /// The HWId of the user. /// The user's ban history. - public abstract Task> GetServerBansAsync(IPAddress? address, NetUserId? userId); + public abstract Task> GetServerBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId); public abstract Task AddServerBanAsync(ServerBanDef serverBan); public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban); @@ -280,14 +289,22 @@ namespace Content.Server.Database /* * PLAYER RECORDS */ - public abstract Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address); + public abstract Task UpdatePlayerRecord( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId); public abstract Task GetPlayerRecordByUserName(string userName, CancellationToken cancel); public abstract Task GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel); /* * CONNECTION LOG */ - public abstract Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + public abstract Task AddConnectionLogAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId); /* * ADMIN STUFF diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index a404d3f749..e79c5bb66e 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Net; using System.Threading; @@ -59,8 +60,12 @@ namespace Content.Server.Database /// /// The ip address of the user. /// The id of the user. + /// The hardware ID of the user. /// The user's latest received un-pardoned ban, or null if none exist. - Task GetServerBanAsync(IPAddress? address, NetUserId? userId); + Task GetServerBanAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId); /// /// Looks up an user's ban history. @@ -69,19 +74,31 @@ namespace Content.Server.Database /// /// The ip address of the user. /// The id of the user. + /// The HWId of the user. /// The user's ban history. - Task> GetServerBansAsync(IPAddress? address, NetUserId? userId); + Task> GetServerBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId); Task AddServerBanAsync(ServerBanDef serverBan); Task AddServerUnbanAsync(ServerUnbanDef serverBan); // Player records - Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address); + Task UpdatePlayerRecordAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId); Task GetPlayerRecordByUserName(string userName, CancellationToken cancel = default); Task GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default); // Connection log - Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); + Task AddConnectionLogAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId); // Admins Task GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default); @@ -179,14 +196,20 @@ namespace Content.Server.Database return _db.GetServerBanAsync(id); } - public Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + public Task GetServerBanAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) { - return _db.GetServerBanAsync(address, userId); + return _db.GetServerBanAsync(address, userId, hwId); } - public Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + public Task> GetServerBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) { - return _db.GetServerBansAsync(address, userId); + return _db.GetServerBansAsync(address, userId, hwId); } public Task AddServerBanAsync(ServerBanDef serverBan) @@ -199,9 +222,13 @@ namespace Content.Server.Database return _db.AddServerUnbanAsync(serverUnban); } - public Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address) + public Task UpdatePlayerRecordAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId) { - return _db.UpdatePlayerRecord(userId, userName, address); + return _db.UpdatePlayerRecord(userId, userName, address, hwId); } public Task GetPlayerRecordByUserName(string userName, CancellationToken cancel = default) @@ -214,9 +241,13 @@ namespace Content.Server.Database return _db.GetPlayerRecordByUserId(userId, cancel); } - public Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + public Task AddConnectionLogAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId) { - return _db.AddConnectionLogAsync(userId, userName, address); + return _db.AddConnectionLogAsync(userId, userName, address, hwId); } public Task GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default) diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index 478f3feb49..5519a9bf37 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Data; using System.Linq; using System.Net; @@ -48,7 +49,10 @@ namespace Content.Server.Database return ConvertBan(ban); } - public override async Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + public override async Task GetServerBanAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) { if (address == null && userId == null) { @@ -57,73 +61,31 @@ namespace Content.Server.Database await using var db = await GetDbImpl(); - var query = db.PgDbContext.Ban - .Include(p => p.Unban) - .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); - - if (userId is { } uid) - { - if (address == null) - { - // Only have a user ID. - query = query.Where(p => p.UserId == uid.UserId); - } - else - { - // Have both user ID and IP address. - query = query.Where(p => - (p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)) - || p.UserId == uid.UserId); - } - } - else - { - // Only have a connecting address. - query = query.Where( - p => p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)); - } + var query = MakeBanLookupQuery(address, userId, hwId, db) + .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)) + .OrderByDescending(b => b.BanTime); var ban = await query.FirstOrDefaultAsync(); return ConvertBan(ban); } - public override async Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + public override async Task> GetServerBansAsync( + IPAddress? address, + 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"); } await using var db = await GetDbImpl(); - var query = db.PgDbContext.Ban - .Include(p => p.Unban).AsQueryable(); - - if (userId is { } uid) - { - if (address == null) - { - // Only have a user ID. - query = query.Where(p => p.UserId == uid.UserId); - } - else - { - // Have both user ID and IP address. - query = query.Where(p => - (p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)) - || p.UserId == uid.UserId); - } - } - else - { - // Only have a connecting address. - query = query.Where( - p => p.Address != null && EF.Functions.ContainsOrEqual(p.Address.Value, address)); - } + var query = MakeBanLookupQuery(address, userId, hwId, db); var queryBans = await query.ToArrayAsync(); - var bans = new List(); + var bans = new List(queryBans.Length); foreach (var ban in queryBans) { @@ -138,6 +100,45 @@ namespace Content.Server.Database return bans; } + private static IQueryable MakeBanLookupQuery( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId, + DbGuardImpl db) + { + IQueryable? query = null; + + if (userId is { } uid) + { + var newQ = db.PgDbContext.Ban + .Include(p => p.Unban) + .Where(b => b.UserId == uid.UserId); + + query = query == null ? newQ : query.Union(newQ); + } + + if (address != null) + { + var newQ = db.PgDbContext.Ban + .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.Ban + .Include(p => p.Unban) + .Where(b => b.HWId!.SequenceEqual(hwId)); + + query = query == null ? newQ : query.Union(newQ); + } + + query = query!.Distinct(); + return query; + } + private static ServerBanDef? ConvertBan(PostgresServerBan? ban) { if (ban == null) @@ -163,6 +164,7 @@ namespace Content.Server.Database ban.Id, uid, ban.Address, + ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), ban.BanTime, ban.ExpirationTime, ban.Reason, @@ -196,6 +198,7 @@ namespace Content.Server.Database db.PgDbContext.Ban.Add(new PostgresServerBan { Address = serverBan.Address, + HWId = serverBan.HWId?.ToArray(), Reason = serverBan.Reason, BanningAdmin = serverBan.BanningAdmin?.UserId, BanTime = serverBan.BanTime.UtcDateTime, @@ -220,7 +223,11 @@ namespace Content.Server.Database await db.PgDbContext.SaveChangesAsync(); } - public override async Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address) + public override async Task UpdatePlayerRecord( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId) { await using var db = await GetDbImpl(); @@ -237,6 +244,7 @@ namespace Content.Server.Database record.LastSeenTime = DateTime.UtcNow; record.LastSeenAddress = address; record.LastSeenUserName = userName; + record.LastSeenHWId = hwId.ToArray(); await db.PgDbContext.SaveChangesAsync(); } @@ -277,10 +285,15 @@ namespace Content.Server.Database new DateTimeOffset(record.FirstSeenTime), record.LastSeenUserName, new DateTimeOffset(record.LastSeenTime), - record.LastSeenAddress); + record.LastSeenAddress, + record.LastSeenHWId?.ToImmutableArray()); } - public override async Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + public override async Task AddConnectionLogAsync( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId) { await using var db = await GetDbImpl(); @@ -289,7 +302,8 @@ namespace Content.Server.Database Address = address, Time = DateTime.UtcNow, UserId = userId.UserId, - UserName = userName + UserName = userName, + HWId = hwId.ToArray() }); await db.PgDbContext.SaveChangesAsync(); diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index c7431b5688..af60837bc6 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Net; @@ -58,7 +59,10 @@ namespace Content.Server.Database return ConvertBan(ban); } - public override async Task GetServerBanAsync(IPAddress? address, NetUserId? userId) + public override async Task GetServerBanAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) { await using var db = await GetDbImpl(); @@ -69,23 +73,15 @@ namespace Content.Server.Database .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow)) .ToListAsync(); - foreach (var ban in bans) - { - if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) - { - return ConvertBan(ban); - } - - if (userId is { } id && ban.UserId == id.UserId) - { - return ConvertBan(ban); - } - } - - return null; + return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId)) is { } foundBan + ? ConvertBan(foundBan) + : null; } - public override async Task> GetServerBansAsync(IPAddress? address, NetUserId? userId) + public override async Task> GetServerBansAsync( + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) { await using var db = await GetDbImpl(); @@ -95,30 +91,34 @@ namespace Content.Server.Database .Include(p => p.Unban) .ToListAsync(); - var bans = new List(); + return queryBans + .Where(b => BanMatches(b, address, userId, hwId)) + .Select(ConvertBan) + .ToList()!; + } - foreach (var ban in queryBans) + private static bool BanMatches( + SqliteServerBan ban, + IPAddress? address, + NetUserId? userId, + ImmutableArray? hwId) + { + if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) { - ServerBanDef? banDef = null; - - if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) - { - banDef = ConvertBan(ban); - } - else if (userId is { } id && ban.UserId == id.UserId) - { - banDef = ConvertBan(ban); - } - - if (banDef == null) - { - continue; - } - - bans.Add(banDef); + return true; } - return bans; + 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 AddServerBanAsync(ServerBanDef serverBan) @@ -136,6 +136,7 @@ namespace Content.Server.Database Address = addrStr, Reason = serverBan.Reason, BanningAdmin = serverBan.BanningAdmin?.UserId, + HWId = serverBan.HWId?.ToArray(), BanTime = serverBan.BanTime.UtcDateTime, ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, UserId = serverBan.UserId?.UserId @@ -158,7 +159,11 @@ namespace Content.Server.Database await db.SqliteDbContext.SaveChangesAsync(); } - public override async Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address) + public override async Task UpdatePlayerRecord( + NetUserId userId, + string userName, + IPAddress address, + ImmutableArray hwId) { await using var db = await GetDbImpl(); @@ -175,6 +180,7 @@ namespace Content.Server.Database record.LastSeenTime = DateTime.UtcNow; record.LastSeenAddress = address.ToString(); record.LastSeenUserName = userName; + record.LastSeenHWId = hwId.ToArray(); await db.SqliteDbContext.SaveChangesAsync(); } @@ -215,8 +221,10 @@ namespace Content.Server.Database new DateTimeOffset(record.FirstSeenTime, TimeSpan.Zero), record.LastSeenUserName, new DateTimeOffset(record.LastSeenTime, TimeSpan.Zero), - IPAddress.Parse(record.LastSeenAddress)); + IPAddress.Parse(record.LastSeenAddress), + record.LastSeenHWId?.ToImmutableArray()); } + private static ServerBanDef? ConvertBan(SqliteServerBan? ban) { if (ban == null) @@ -225,13 +233,13 @@ namespace Content.Server.Database } NetUserId? uid = null; - if (ban.UserId is {} guid) + if (ban.UserId is { } guid) { uid = new NetUserId(guid); } NetUserId? aUid = null; - if (ban.BanningAdmin is {} aGuid) + if (ban.BanningAdmin is { } aGuid) { aUid = new NetUserId(aGuid); } @@ -250,6 +258,7 @@ namespace Content.Server.Database ban.Id, uid, addrTuple, + ban.HWId == null ? null : ImmutableArray.Create(ban.HWId), ban.BanTime, ban.ExpirationTime, ban.Reason, @@ -265,7 +274,7 @@ namespace Content.Server.Database } NetUserId? aUid = null; - if (unban.UnbanningAdmin is {} aGuid) + if (unban.UnbanningAdmin is { } aGuid) { aUid = new NetUserId(aGuid); } @@ -276,7 +285,8 @@ namespace Content.Server.Database unban.UnbanTime); } - public override async Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) + public override async Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address, + ImmutableArray hwId) { await using var db = await GetDbImpl(); @@ -285,7 +295,8 @@ namespace Content.Server.Database Address = address.ToString(), Time = DateTime.UtcNow, UserId = userId.UserId, - UserName = userName + UserName = userName, + HWId = hwId.ToArray() }); await db.SqliteDbContext.SaveChangesAsync();