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();