Use HWIDs for bans.

This commit is contained in:
Pieter-Jan Briers
2021-03-22 01:30:50 +01:00
parent 071362ed25
commit a321b4302e
19 changed files with 1589 additions and 181 deletions

View File

@@ -31,17 +31,17 @@ namespace Content.IntegrationTests.Tests.Commands
var clientId = clientSession.UserId; var clientId = clientSession.UserId;
// No bans on record // 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.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 // Try to pardon a ban that does not exist
sConsole.ExecuteCommand("pardon 1"); sConsole.ExecuteCommand("pardon 1");
// Still no bans on record // 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.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"; var banReason = "test";
@@ -49,20 +49,20 @@ namespace Content.IntegrationTests.Tests.Commands
sConsole.ExecuteCommand($"ban {clientSession.Name} {banReason} 1440"); sConsole.ExecuteCommand($"ban {clientSession.Name} {banReason} 1440");
// Should have one ban on record now // 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.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 // Try to pardon a ban that does not exist
sConsole.ExecuteCommand("pardon 2"); sConsole.ExecuteCommand("pardon 2");
// The existing ban is unaffected // 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); var ban = await sDatabase.GetServerBanAsync(1);
Assert.That(ban, Is.Not.Null); 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 // Check that it matches
Assert.That(ban.Id, Is.EqualTo(1)); Assert.That(ban.Id, Is.EqualTo(1));
@@ -81,14 +81,14 @@ namespace Content.IntegrationTests.Tests.Commands
sConsole.ExecuteCommand("pardon 1"); sConsole.ExecuteCommand("pardon 1");
// No bans should be returned // 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 // Direct id lookup returns a pardoned ban
var pardonedBan = await sDatabase.GetServerBanAsync(1); var pardonedBan = await sDatabase.GetServerBanAsync(1);
Assert.That(pardonedBan, Is.Not.Null); Assert.That(pardonedBan, Is.Not.Null);
// The list is still returned since that ignores pardons // 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 // Check that it matches
Assert.That(pardonedBan.Id, Is.EqualTo(1)); Assert.That(pardonedBan.Id, Is.EqualTo(1));
@@ -114,13 +114,13 @@ namespace Content.IntegrationTests.Tests.Commands
// Nothing changes // Nothing changes
// No bans should be returned // 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 // Direct id lookup returns a pardoned ban
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null); Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
// The list is still returned since that ignores pardons // 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));
}); });
} }
} }

View File

@@ -0,0 +1,592 @@
// <auto-generated />
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<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<int?>("AdminRankId")
.HasColumnType("integer")
.HasColumnName("admin_rank_id");
b.Property<string>("Title")
.HasColumnType("text")
.HasColumnName("title");
b.HasKey("UserId");
b.HasIndex("AdminRankId");
b.ToTable("admin");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_flag_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<Guid>("AdminId")
.HasColumnType("uuid")
.HasColumnName("admin_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("text")
.HasColumnName("flag");
b.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_rank_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.HasKey("Id");
b.ToTable("admin_rank");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_rank_flag_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("AdminRankId")
.HasColumnType("integer")
.HasColumnName("admin_rank_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("antag_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AntagName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("antag_name");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("assigned_user_id_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("job_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("job_name");
b.Property<int>("Priority")
.HasColumnType("integer")
.HasColumnName("priority");
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("job");
});
modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("connection_log_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<IPAddress>("Address")
.IsRequired()
.HasColumnType("inet")
.HasColumnName("address");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<DateTime>("Time")
.HasColumnType("timestamp with time zone")
.HasColumnName("time");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("player_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<DateTime>("FirstSeenTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time");
b.Property<IPAddress>("LastSeenAddress")
.IsRequired()
.HasColumnType("inet")
.HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("bytea")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_time");
b.Property<string>("LastSeenUserName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("last_seen_user_name");
b.Property<Guid>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("server_ban_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<ValueTuple<IPAddress, int>?>("Address")
.HasColumnType("inet")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("uuid")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text")
.HasColumnName("reason");
b.Property<Guid?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("unban_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("BanId")
.HasColumnType("integer")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("unban_time");
b.Property<Guid?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("preference_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AdminOOCColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("admin_ooc_color");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("integer")
.HasColumnName("selected_character_slot");
b.Property<Guid>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("profile_id")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("Age")
.HasColumnType("integer")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("text")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("text")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("eye_color");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("facial_hair_name");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("text")
.HasColumnName("gender");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("hair_name");
b.Property<int>("PreferenceId")
.HasColumnType("integer")
.HasColumnName("preference_id");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("integer")
.HasColumnName("pref_unavailable");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("text")
.HasColumnName("sex");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("text")
.HasColumnName("skin_color");
b.Property<int>("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
}
}
}

View File

@@ -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<byte[]>(
name: "hwid",
table: "server_ban",
type: "bytea",
nullable: true);
migrationBuilder.AddColumn<byte[]>(
name: "last_seen_hwid",
table: "player",
type: "bytea",
nullable: true);
migrationBuilder.AddColumn<byte[]>(
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");
}
}
}

View File

@@ -16,9 +16,9 @@ namespace Content.Server.Database.Migrations.Postgres
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.UseIdentityByDefaultColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 63) .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 => modelBuilder.Entity("Content.Server.Database.Admin", b =>
{ {
@@ -48,7 +48,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("admin_flag_id") .HasColumnName("admin_flag_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<Guid>("AdminId") b.Property<Guid>("AdminId")
.HasColumnType("uuid") .HasColumnType("uuid")
@@ -79,7 +79,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("admin_rank_id") .HasColumnName("admin_rank_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
@@ -97,7 +97,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("admin_rank_flag_id") .HasColumnName("admin_rank_flag_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("AdminRankId") b.Property<int>("AdminRankId")
.HasColumnType("integer") .HasColumnType("integer")
@@ -124,7 +124,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("antag_id") .HasColumnName("antag_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AntagName") b.Property<string>("AntagName")
.IsRequired() .IsRequired()
@@ -149,7 +149,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("assigned_user_id_id") .HasColumnName("assigned_user_id_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<Guid>("UserId") b.Property<Guid>("UserId")
.HasColumnType("uuid") .HasColumnType("uuid")
@@ -177,7 +177,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("job_id") .HasColumnName("job_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("JobName") b.Property<string>("JobName")
.IsRequired() .IsRequired()
@@ -205,13 +205,17 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("connection_log_id") .HasColumnName("connection_log_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<IPAddress>("Address") b.Property<IPAddress>("Address")
.IsRequired() .IsRequired()
.HasColumnType("inet") .HasColumnType("inet")
.HasColumnName("address"); .HasColumnName("address");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<DateTime>("Time") b.Property<DateTime>("Time")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("time"); .HasColumnName("time");
@@ -240,7 +244,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("player_id") .HasColumnName("player_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<DateTime>("FirstSeenTime") b.Property<DateTime>("FirstSeenTime")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
@@ -251,6 +255,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("inet") .HasColumnType("inet")
.HasColumnName("last_seen_address"); .HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("bytea")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime") b.Property<DateTime>("LastSeenTime")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_time"); .HasColumnName("last_seen_time");
@@ -282,7 +290,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("server_ban_id") .HasColumnName("server_ban_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<ValueTuple<IPAddress, int>?>("Address") b.Property<ValueTuple<IPAddress, int>?>("Address")
.HasColumnType("inet") .HasColumnType("inet")
@@ -300,6 +308,10 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<string>("Reason") b.Property<string>("Reason")
.IsRequired() .IsRequired()
.HasColumnType("text") .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("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 => modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b =>
@@ -328,7 +340,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("unban_id") .HasColumnName("unban_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("BanId") b.Property<int>("BanId")
.HasColumnType("integer") .HasColumnType("integer")
@@ -356,7 +368,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("preference_id") .HasColumnName("preference_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("AdminOOCColor") b.Property<string>("AdminOOCColor")
.IsRequired() .IsRequired()
@@ -385,7 +397,7 @@ namespace Content.Server.Database.Migrations.Postgres
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("profile_id") .HasColumnName("profile_id")
.UseIdentityByDefaultColumn(); .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int>("Age") b.Property<int>("Age")
.HasColumnType("integer") .HasColumnType("integer")

View File

@@ -0,0 +1,559 @@
// <auto-generated />
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<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<int?>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Title")
.HasColumnType("TEXT")
.HasColumnName("title");
b.HasKey("UserId");
b.HasIndex("AdminRankId");
b.ToTable("admin");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_flag_id");
b.Property<Guid>("AdminId")
.HasColumnType("TEXT")
.HasColumnName("admin_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("flag");
b.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Id");
b.ToTable("admin_rank");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_flag_id");
b.Property<int>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("antag_id");
b.Property<string>("AntagName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("antag_name");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("assigned_user_id_id");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("job_id");
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("job_name");
b.Property<int>("Priority")
.HasColumnType("INTEGER")
.HasColumnName("priority");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("job");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<string>("AdminOOCColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("admin_ooc_color");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER")
.HasColumnName("selected_character_slot");
b.Property<Guid>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.Property<int>("Age")
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("eye_color");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_name");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("gender");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_name");
b.Property<int>("PreferenceId")
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("INTEGER")
.HasColumnName("pref_unavailable");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("sex");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("skin_color");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("connection_log_id");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<DateTime>("Time")
.HasColumnType("TEXT")
.HasColumnName("time");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("user_name");
b.HasKey("Id");
b.ToTable("connection_log");
});
modelBuilder.Entity("Content.Server.Database.SqlitePlayer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("player_id");
b.Property<DateTime>("FirstSeenTime")
.HasColumnType("TEXT")
.HasColumnName("first_seen_time");
b.Property<string>("LastSeenAddress")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("BLOB")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime")
.HasColumnType("TEXT")
.HasColumnName("last_seen_time");
b.Property<string>("LastSeenUserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_user_name");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("LastSeenUserName");
b.ToTable("player");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<string>("Address")
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("TEXT")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id");
b.ToTable("ban");
});
modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("unban_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("TEXT")
.HasColumnName("unban_time");
b.Property<Guid?>("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
}
}
}

View File

@@ -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<byte[]>(
name: "last_seen_hwid",
table: "player",
type: "BLOB",
nullable: true);
migrationBuilder.AddColumn<byte[]>(
name: "hwid",
table: "connection_log",
type: "BLOB",
nullable: true);
migrationBuilder.AddColumn<byte[]>(
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");
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Database.Migrations.Sqlite
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "5.0.0"); .HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("Content.Server.Database.Admin", b => modelBuilder.Entity("Content.Server.Database.Admin", b =>
{ {
@@ -317,6 +317,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("address"); .HasColumnName("address");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<DateTime>("Time") b.Property<DateTime>("Time")
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("time"); .HasColumnName("time");
@@ -351,6 +355,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("last_seen_address"); .HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("BLOB")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime") b.Property<DateTime>("LastSeenTime")
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("last_seen_time"); .HasColumnName("last_seen_time");
@@ -394,6 +402,10 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT") .HasColumnType("TEXT")
.HasColumnName("expiration_time"); .HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<string>("Reason") b.Property<string>("Reason")
.IsRequired() .IsRequired()
.HasColumnType("TEXT") .HasColumnType("TEXT")

View File

@@ -54,7 +54,7 @@ namespace Content.Server.Database
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes. // So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
modelBuilder.Entity<PostgresServerBan>() modelBuilder.Entity<PostgresServerBan>()
.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address") .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<PostgresPlayer>() modelBuilder.Entity<PostgresPlayer>()
.HasIndex(p => p.UserId) .HasIndex(p => p.UserId)
@@ -84,6 +84,7 @@ namespace Content.Server.Database
[Column("user_id")] public Guid? UserId { get; set; } [Column("user_id")] public Guid? UserId { get; set; }
[Column("address", TypeName = "inet")] public (IPAddress, int)? Address { 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")] [Column("ban_time", TypeName = "timestamp with time zone")]
public DateTime BanTime { get; set; } public DateTime BanTime { get; set; }
@@ -129,6 +130,7 @@ namespace Content.Server.Database
public DateTime LastSeenTime { get; set; } public DateTime LastSeenTime { get; set; }
[Column("last_seen_address")] public IPAddress LastSeenAddress { get; set; } = null!; [Column("last_seen_address")] public IPAddress LastSeenAddress { get; set; } = null!;
[Column("last_seen_hwid")] public byte[]? LastSeenHWId { get; set; }
} }
[Table("connection_log")] [Table("connection_log")]
@@ -143,5 +145,6 @@ namespace Content.Server.Database
public DateTime Time { get; set; } public DateTime Time { get; set; }
[Column("address")] public IPAddress Address { get; set; } = null!; [Column("address")] public IPAddress Address { get; set; } = null!;
[Column("hwid")] public byte[]? HWId { get; set; }
} }
} }

View File

@@ -41,6 +41,7 @@ namespace Content.Server.Database
[Column("user_id")] public Guid? UserId { get; set; } [Column("user_id")] public Guid? UserId { get; set; }
[Column("address")] public string? Address { 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("ban_time")] public DateTime BanTime { get; set; }
[Column("expiration_time")] public DateTime? ExpirationTime { 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_user_name")] public string LastSeenUserName { get; set; } = null!;
[Column("last_seen_time")] public DateTime LastSeenTime { get; set; } [Column("last_seen_time")] public DateTime LastSeenTime { get; set; }
[Column("last_seen_address")] public string LastSeenAddress { get; set; } = null!; [Column("last_seen_address")] public string LastSeenAddress { get; set; } = null!;
[Column("last_seen_hwid")] public byte[]? LastSeenHWId { get; set; }
} }
[Table("connection_log")] [Table("connection_log")]
@@ -86,5 +88,6 @@ namespace Content.Server.Database
[Column("user_name")] public string UserName { get; set; } = null!; [Column("user_name")] public string UserName { get; set; } = null!;
[Column("time")] public DateTime Time { get; set; } [Column("time")] public DateTime Time { get; set; }
[Column("address")] public string Address { get; set; } = null!; [Column("address")] public string Address { get; set; } = null!;
[Column("hwid")] public byte[]? HWId { get; set; }
} }
} }

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Net;
using System.Net.Sockets;
using System.Text; using System.Text;
using Content.Server.Database; using Content.Server.Database;
using Content.Shared.Administration; using Content.Shared.Administration;
@@ -51,14 +53,16 @@ namespace Content.Server.Administration.Commands
return; return;
} }
var resolvedUid = await locator.LookupIdByNameOrIdAsync(target); var located = await locator.LookupIdByNameOrIdAsync(target);
if (resolvedUid == null) if (located == null)
{ {
shell.WriteError("Unable to find a player with that name."); shell.WriteError("Unable to find a player with that name.");
return; return;
} }
var targetUid = resolvedUid.Value; var targetUid = located.UserId;
var targetHWid = located.LastHWId;
var targetAddr = located.LastAddress;
if (player != null && player.UserId == targetUid) if (player != null && player.UserId == targetUid)
{ {
@@ -72,7 +76,29 @@ namespace Content.Server.Administration.Commands
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); 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}\""); var response = new StringBuilder($"Banned {target} with reason \"{reason}\"");

View File

@@ -44,7 +44,7 @@ namespace Content.Server.Administration.Commands
return; return;
} }
var bans = await dbMan.GetServerBansAsync(null, targetUid); var bans = await dbMan.GetServerBansAsync(null, targetUid, null);
if (bans.Count == 0) if (bans.Count == 0)
{ {

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Immutable;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
@@ -15,6 +16,8 @@ using Robust.Shared.Network;
namespace Content.Server.Administration namespace Content.Server.Administration
{ {
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId);
/// <summary> /// <summary>
/// Utilities for finding user IDs that extend to more than the server database. /// Utilities for finding user IDs that extend to more than the server database.
/// </summary> /// </summary>
@@ -28,19 +31,13 @@ namespace Content.Server.Administration
/// Look up a user ID by name globally. /// Look up a user ID by name globally.
/// </summary> /// </summary>
/// <returns>Null if the player does not exist.</returns> /// <returns>Null if the player does not exist.</returns>
Task<NetUserId?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default); Task<LocatedPlayerData?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default);
/// <summary> /// <summary>
/// If passed a GUID, runs <see cref="DoesPlayerExistAsync"/> 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 <see cref="LookupIdByNameAsync"/>. /// If passed a player name, returns <see cref="LookupIdByNameAsync"/>.
/// </summary> /// </summary>
Task<NetUserId?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default); Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default);
/// <summary>
/// Checks whether the specified user ID is an existing account, globally.
/// </summary>
/// <returns>True if the player account exists, false otherwise</returns>
Task<bool> DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default);
} }
internal sealed class PlayerLocator : IPlayerLocator internal sealed class PlayerLocator : IPlayerLocator
@@ -49,22 +46,27 @@ namespace Content.Server.Administration
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerDbManager _db = default!;
public async Task<NetUserId?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default) public async Task<LocatedPlayerData?> LookupIdByNameAsync(string playerName, CancellationToken cancel = default)
{ {
// Check people currently on the server, the easiest case. // Check people currently on the server, the easiest case.
if (_playerManager.TryGetSessionByUsername(playerName, out var session)) 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. // Check database for past players.
var record = await _db.GetPlayerRecordByUserName(playerName, cancel); var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
if (record != null) if (record != null)
return record.UserId; return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId);
// If all else fails, ask the auth server. // If all else fails, ask the auth server.
var client = new HttpClient(); var client = new HttpClient();
var authServer = _configurationManager.GetCVar(CVars.AuthServer); var authServer = _configurationManager.GetCVar(CVars.AuthServer);
var resp = await client.GetAsync($"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}", var requestUri = $"{authServer}api/query/name?name={WebUtility.UrlEncode(playerName)}";
cancel); using var resp = await client.GetAsync(requestUri, cancel);
if (resp.StatusCode == HttpStatusCode.NotFound) if (resp.StatusCode == HttpStatusCode.NotFound)
return null; return null;
@@ -83,45 +85,49 @@ namespace Content.Server.Administration
return null; return null;
} }
return new NetUserId(responseData.UserId); return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null);
} }
public async Task<bool> DoesPlayerExistAsync(NetUserId userId, CancellationToken cancel = default) public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
{ {
// Check people currently on the server, the easiest case. // Check people currently on the server, the easiest case.
if (_playerManager.ValidSessionId(userId)) if (_playerManager.TryGetSessionById(userId, out var session))
return true; {
var address = session.ConnectedClient.RemoteEndPoint.Address;
var hwId = session.ConnectedClient.UserData.HWId;
return new LocatedPlayerData(userId, address, hwId);
}
// Check database for past players. // Check database for past players.
var record = await _db.GetPlayerRecordByUserId(userId, cancel); var record = await _db.GetPlayerRecordByUserId(userId, cancel);
if (record != null) if (record != null)
return true; return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId);
// If all else fails, ask the auth server. // If all else fails, ask the auth server.
var client = new HttpClient(); var client = new HttpClient();
var authServer = _configurationManager.GetCVar(CVars.AuthServer); var authServer = _configurationManager.GetCVar(CVars.AuthServer);
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}"; 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) if (resp.StatusCode == HttpStatusCode.NotFound)
return false; return null;
if (!resp.IsSuccessStatusCode) if (!resp.IsSuccessStatusCode)
{ {
Logger.ErrorS("PlayerLocate", "Auth server returned bad response {StatusCode}!", resp.StatusCode); 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<NetUserId?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default) public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
{ {
if (Guid.TryParse(playerName, out var guid)) if (Guid.TryParse(playerName, out var guid))
{ {
var userId = new NetUserId(guid); var userId = new NetUserId(guid);
return await DoesPlayerExistAsync(userId, cancel) ? userId : null; return await LookupIdAsync(userId, cancel);
} }
return await LookupIdByNameAsync(playerName, cancel); return await LookupIdByNameAsync(playerName, cancel);

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Database; using Content.Server.Database;
using Content.Server.Preferences; using Content.Server.Preferences;
@@ -61,7 +62,14 @@ The ban reason is: ""{ban.Reason}""
// Check if banned. // Check if banned.
var addr = e.IP.Address; var addr = e.IP.Address;
var userId = e.UserId; var userId = e.UserId;
var ban = await _db.GetServerBanAsync(addr, userId); ImmutableArray<byte>? 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) if (ban != null)
{ {
var expires = "This is a permanent ban."; var expires = "This is a permanent ban.";
@@ -83,8 +91,8 @@ The ban reason is: ""{ban.Reason}""
return; return;
} }
await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr); await _db.UpdatePlayerRecordAsync(userId, e.UserName, addr, e.UserData.HWId);
await _db.AddConnectionLogAsync(userId, e.UserName, addr); await _db.AddConnectionLogAsync(userId, e.UserName, addr, e.UserData.HWId);
} }
private async Task<NetUserId?> AssignUserIdCallback(string name) private async Task<NetUserId?> AssignUserIdCallback(string name)

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Immutable;
using System.Net; using System.Net;
using Robust.Shared.Network; using Robust.Shared.Network;
@@ -7,6 +8,7 @@ namespace Content.Server.Database
public sealed class PlayerRecord public sealed class PlayerRecord
{ {
public NetUserId UserId { get; } public NetUserId UserId { get; }
public ImmutableArray<byte>? HWId { get; }
public DateTimeOffset FirstSeenTime { get; } public DateTimeOffset FirstSeenTime { get; }
public string LastSeenUserName { get; } public string LastSeenUserName { get; }
public DateTimeOffset LastSeenTime { get; } public DateTimeOffset LastSeenTime { get; }
@@ -17,13 +19,15 @@ namespace Content.Server.Database
DateTimeOffset firstSeenTime, DateTimeOffset firstSeenTime,
string lastSeenUserName, string lastSeenUserName,
DateTimeOffset lastSeenTime, DateTimeOffset lastSeenTime,
IPAddress lastSeenAddress) IPAddress lastSeenAddress,
ImmutableArray<byte>? hwId)
{ {
UserId = userId; UserId = userId;
FirstSeenTime = firstSeenTime; FirstSeenTime = firstSeenTime;
LastSeenUserName = lastSeenUserName; LastSeenUserName = lastSeenUserName;
LastSeenTime = lastSeenTime; LastSeenTime = lastSeenTime;
LastSeenAddress = lastSeenAddress; LastSeenAddress = lastSeenAddress;
HWId = hwId;
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Immutable;
using System.Net; using System.Net;
using Robust.Shared.Network; using Robust.Shared.Network;
@@ -11,6 +12,7 @@ namespace Content.Server.Database
public int? Id { get; } public int? Id { get; }
public NetUserId? UserId { get; } public NetUserId? UserId { get; }
public (IPAddress address, int cidrMask)? Address { get; } public (IPAddress address, int cidrMask)? Address { get; }
public ImmutableArray<byte>? HWId { get; }
public DateTimeOffset BanTime { get; } public DateTimeOffset BanTime { get; }
public DateTimeOffset? ExpirationTime { get; } public DateTimeOffset? ExpirationTime { get; }
@@ -22,15 +24,16 @@ namespace Content.Server.Database
int? id, int? id,
NetUserId? userId, NetUserId? userId,
(IPAddress, int)? address, (IPAddress, int)? address,
ImmutableArray<byte>? hwId,
DateTimeOffset banTime, DateTimeOffset banTime,
DateTimeOffset? expirationTime, DateTimeOffset? expirationTime,
string reason, string reason,
NetUserId? banningAdmin, NetUserId? banningAdmin,
ServerUnbanDef? unban) 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) if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
@@ -43,6 +46,7 @@ namespace Content.Server.Database
Id = id; Id = id;
UserId = userId; UserId = userId;
Address = address; Address = address;
HWId = hwId;
BanTime = banTime; BanTime = banTime;
ExpirationTime = expirationTime; ExpirationTime = expirationTime;
Reason = reason; Reason = reason;

View File

@@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@@ -261,8 +262,12 @@ namespace Content.Server.Database
/// </summary> /// </summary>
/// <param name="address">The ip address of the user.</param> /// <param name="address">The ip address of the user.</param>
/// <param name="userId">The id of the user.</param> /// <param name="userId">The id of the user.</param>
/// <param name="hwId">The HWId of the user.</param>
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns> /// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
public abstract Task<ServerBanDef?> GetServerBanAsync(IPAddress? address, NetUserId? userId); public abstract Task<ServerBanDef?> GetServerBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId);
/// <summary> /// <summary>
/// Looks up an user's ban history. /// Looks up an user's ban history.
@@ -271,8 +276,12 @@ namespace Content.Server.Database
/// </summary> /// </summary>
/// <param name="address">The ip address of the user.</param> /// <param name="address">The ip address of the user.</param>
/// <param name="userId">The id of the user.</param> /// <param name="userId">The id of the user.</param>
/// <param name="hwId">The HWId of the user.</param>
/// <returns>The user's ban history.</returns> /// <returns>The user's ban history.</returns>
public abstract Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address, NetUserId? userId); public abstract Task<List<ServerBanDef>> GetServerBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId);
public abstract Task AddServerBanAsync(ServerBanDef serverBan); public abstract Task AddServerBanAsync(ServerBanDef serverBan);
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban); public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
@@ -280,14 +289,22 @@ namespace Content.Server.Database
/* /*
* PLAYER RECORDS * PLAYER RECORDS
*/ */
public abstract Task UpdatePlayerRecord(NetUserId userId, string userName, IPAddress address); public abstract Task UpdatePlayerRecord(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId);
public abstract Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel); public abstract Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel);
public abstract Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel); public abstract Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel);
/* /*
* CONNECTION LOG * CONNECTION LOG
*/ */
public abstract Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); public abstract Task AddConnectionLogAsync(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId);
/* /*
* ADMIN STUFF * ADMIN STUFF

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@@ -59,8 +60,12 @@ namespace Content.Server.Database
/// </summary> /// </summary>
/// <param name="address">The ip address of the user.</param> /// <param name="address">The ip address of the user.</param>
/// <param name="userId">The id of the user.</param> /// <param name="userId">The id of the user.</param>
/// <param name="hwId">The hardware ID of the user.</param>
/// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns> /// <returns>The user's latest received un-pardoned ban, or null if none exist.</returns>
Task<ServerBanDef?> GetServerBanAsync(IPAddress? address, NetUserId? userId); Task<ServerBanDef?> GetServerBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId);
/// <summary> /// <summary>
/// Looks up an user's ban history. /// Looks up an user's ban history.
@@ -69,19 +74,31 @@ namespace Content.Server.Database
/// </summary> /// </summary>
/// <param name="address">The ip address of the user.</param> /// <param name="address">The ip address of the user.</param>
/// <param name="userId">The id of the user.</param> /// <param name="userId">The id of the user.</param>
/// <param name="hwId">The HWId of the user.</param>
/// <returns>The user's ban history.</returns> /// <returns>The user's ban history.</returns>
Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address, NetUserId? userId); Task<List<ServerBanDef>> GetServerBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId);
Task AddServerBanAsync(ServerBanDef serverBan); Task AddServerBanAsync(ServerBanDef serverBan);
Task AddServerUnbanAsync(ServerUnbanDef serverBan); Task AddServerUnbanAsync(ServerUnbanDef serverBan);
// Player records // Player records
Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address); Task UpdatePlayerRecordAsync(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId);
Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default); Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default);
Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default); Task<PlayerRecord?> GetPlayerRecordByUserId(NetUserId userId, CancellationToken cancel = default);
// Connection log // Connection log
Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address); Task AddConnectionLogAsync(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId);
// Admins // Admins
Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default); Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default);
@@ -179,14 +196,20 @@ namespace Content.Server.Database
return _db.GetServerBanAsync(id); return _db.GetServerBanAsync(id);
} }
public Task<ServerBanDef?> GetServerBanAsync(IPAddress? address, NetUserId? userId) public Task<ServerBanDef?> GetServerBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{ {
return _db.GetServerBanAsync(address, userId); return _db.GetServerBanAsync(address, userId, hwId);
} }
public Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address, NetUserId? userId) public Task<List<ServerBanDef>> GetServerBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{ {
return _db.GetServerBansAsync(address, userId); return _db.GetServerBansAsync(address, userId, hwId);
} }
public Task AddServerBanAsync(ServerBanDef serverBan) public Task AddServerBanAsync(ServerBanDef serverBan)
@@ -199,9 +222,13 @@ namespace Content.Server.Database
return _db.AddServerUnbanAsync(serverUnban); return _db.AddServerUnbanAsync(serverUnban);
} }
public Task UpdatePlayerRecordAsync(NetUserId userId, string userName, IPAddress address) public Task UpdatePlayerRecordAsync(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId)
{ {
return _db.UpdatePlayerRecord(userId, userName, address); return _db.UpdatePlayerRecord(userId, userName, address, hwId);
} }
public Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default) public Task<PlayerRecord?> GetPlayerRecordByUserName(string userName, CancellationToken cancel = default)
@@ -214,9 +241,13 @@ namespace Content.Server.Database
return _db.GetPlayerRecordByUserId(userId, cancel); return _db.GetPlayerRecordByUserId(userId, cancel);
} }
public Task AddConnectionLogAsync(NetUserId userId, string userName, IPAddress address) public Task AddConnectionLogAsync(
NetUserId userId,
string userName,
IPAddress address,
ImmutableArray<byte> hwId)
{ {
return _db.AddConnectionLogAsync(userId, userName, address); return _db.AddConnectionLogAsync(userId, userName, address, hwId);
} }
public Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default) public Task<Admin?> GetAdminDataForAsync(NetUserId userId, CancellationToken cancel = default)

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -48,7 +49,10 @@ namespace Content.Server.Database
return ConvertBan(ban); return ConvertBan(ban);
} }
public override async Task<ServerBanDef?> GetServerBanAsync(IPAddress? address, NetUserId? userId) public override async Task<ServerBanDef?> GetServerBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{ {
if (address == null && userId == null) if (address == null && userId == null)
{ {
@@ -57,73 +61,31 @@ namespace Content.Server.Database
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
var query = db.PgDbContext.Ban var query = MakeBanLookupQuery(address, userId, hwId, db)
.Include(p => p.Unban) .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now))
.Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); .OrderByDescending(b => b.BanTime);
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 ban = await query.FirstOrDefaultAsync(); var ban = await query.FirstOrDefaultAsync();
return ConvertBan(ban); return ConvertBan(ban);
} }
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address, NetUserId? userId) public override async Task<List<ServerBanDef>> GetServerBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{ {
if (address == null && userId == null) if (address == null && userId == null && hwId == null)
{ {
throw new ArgumentException("Address and userId cannot both be null"); throw new ArgumentException("Address and userId cannot both be null");
} }
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
var query = db.PgDbContext.Ban var query = MakeBanLookupQuery(address, userId, hwId, db);
.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 queryBans = await query.ToArrayAsync(); var queryBans = await query.ToArrayAsync();
var bans = new List<ServerBanDef>(); var bans = new List<ServerBanDef>(queryBans.Length);
foreach (var ban in queryBans) foreach (var ban in queryBans)
{ {
@@ -138,6 +100,45 @@ namespace Content.Server.Database
return bans; return bans;
} }
private static IQueryable<PostgresServerBan> MakeBanLookupQuery(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
DbGuardImpl db)
{
IQueryable<PostgresServerBan>? 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) private static ServerBanDef? ConvertBan(PostgresServerBan? ban)
{ {
if (ban == null) if (ban == null)
@@ -163,6 +164,7 @@ namespace Content.Server.Database
ban.Id, ban.Id,
uid, uid,
ban.Address, ban.Address,
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime, ban.BanTime,
ban.ExpirationTime, ban.ExpirationTime,
ban.Reason, ban.Reason,
@@ -196,6 +198,7 @@ namespace Content.Server.Database
db.PgDbContext.Ban.Add(new PostgresServerBan db.PgDbContext.Ban.Add(new PostgresServerBan
{ {
Address = serverBan.Address, Address = serverBan.Address,
HWId = serverBan.HWId?.ToArray(),
Reason = serverBan.Reason, Reason = serverBan.Reason,
BanningAdmin = serverBan.BanningAdmin?.UserId, BanningAdmin = serverBan.BanningAdmin?.UserId,
BanTime = serverBan.BanTime.UtcDateTime, BanTime = serverBan.BanTime.UtcDateTime,
@@ -220,7 +223,11 @@ namespace Content.Server.Database
await db.PgDbContext.SaveChangesAsync(); 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<byte> hwId)
{ {
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
@@ -237,6 +244,7 @@ namespace Content.Server.Database
record.LastSeenTime = DateTime.UtcNow; record.LastSeenTime = DateTime.UtcNow;
record.LastSeenAddress = address; record.LastSeenAddress = address;
record.LastSeenUserName = userName; record.LastSeenUserName = userName;
record.LastSeenHWId = hwId.ToArray();
await db.PgDbContext.SaveChangesAsync(); await db.PgDbContext.SaveChangesAsync();
} }
@@ -277,10 +285,15 @@ namespace Content.Server.Database
new DateTimeOffset(record.FirstSeenTime), new DateTimeOffset(record.FirstSeenTime),
record.LastSeenUserName, record.LastSeenUserName,
new DateTimeOffset(record.LastSeenTime), 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<byte> hwId)
{ {
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
@@ -289,7 +302,8 @@ namespace Content.Server.Database
Address = address, Address = address,
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
UserId = userId.UserId, UserId = userId.UserId,
UserName = userName UserName = userName,
HWId = hwId.ToArray()
}); });
await db.PgDbContext.SaveChangesAsync(); await db.PgDbContext.SaveChangesAsync();

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -58,7 +59,10 @@ namespace Content.Server.Database
return ConvertBan(ban); return ConvertBan(ban);
} }
public override async Task<ServerBanDef?> GetServerBanAsync(IPAddress? address, NetUserId? userId) public override async Task<ServerBanDef?> GetServerBanAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{ {
await using var db = await GetDbImpl(); 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)) .Where(p => p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow))
.ToListAsync(); .ToListAsync();
foreach (var ban in bans) return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId)) is { } foundBan
{ ? ConvertBan(foundBan)
if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) : null;
{
return ConvertBan(ban);
} }
if (userId is { } id && ban.UserId == id.UserId) public override async Task<List<ServerBanDef>> GetServerBansAsync(
{ IPAddress? address,
return ConvertBan(ban); NetUserId? userId,
} ImmutableArray<byte>? hwId)
}
return null;
}
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address, NetUserId? userId)
{ {
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
@@ -95,30 +91,34 @@ namespace Content.Server.Database
.Include(p => p.Unban) .Include(p => p.Unban)
.ToListAsync(); .ToListAsync();
var bans = new List<ServerBanDef>(); 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<byte>? hwId)
{ {
ServerBanDef? banDef = null;
if (address != null && ban.Address != null && address.IsInSubnet(ban.Address)) if (address != null && ban.Address != null && address.IsInSubnet(ban.Address))
{ {
banDef = ConvertBan(ban); return true;
} }
else if (userId is { } id && ban.UserId == id.UserId)
if (userId is { } id && ban.UserId == id.UserId)
{ {
banDef = ConvertBan(ban); return true;
} }
if (banDef == null) if (hwId is { } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId))
{ {
continue; return true;
} }
bans.Add(banDef); return false;
}
return bans;
} }
public override async Task AddServerBanAsync(ServerBanDef serverBan) public override async Task AddServerBanAsync(ServerBanDef serverBan)
@@ -136,6 +136,7 @@ namespace Content.Server.Database
Address = addrStr, Address = addrStr,
Reason = serverBan.Reason, Reason = serverBan.Reason,
BanningAdmin = serverBan.BanningAdmin?.UserId, BanningAdmin = serverBan.BanningAdmin?.UserId,
HWId = serverBan.HWId?.ToArray(),
BanTime = serverBan.BanTime.UtcDateTime, BanTime = serverBan.BanTime.UtcDateTime,
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime, ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
UserId = serverBan.UserId?.UserId UserId = serverBan.UserId?.UserId
@@ -158,7 +159,11 @@ namespace Content.Server.Database
await db.SqliteDbContext.SaveChangesAsync(); 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<byte> hwId)
{ {
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
@@ -175,6 +180,7 @@ namespace Content.Server.Database
record.LastSeenTime = DateTime.UtcNow; record.LastSeenTime = DateTime.UtcNow;
record.LastSeenAddress = address.ToString(); record.LastSeenAddress = address.ToString();
record.LastSeenUserName = userName; record.LastSeenUserName = userName;
record.LastSeenHWId = hwId.ToArray();
await db.SqliteDbContext.SaveChangesAsync(); await db.SqliteDbContext.SaveChangesAsync();
} }
@@ -215,8 +221,10 @@ namespace Content.Server.Database
new DateTimeOffset(record.FirstSeenTime, TimeSpan.Zero), new DateTimeOffset(record.FirstSeenTime, TimeSpan.Zero),
record.LastSeenUserName, record.LastSeenUserName,
new DateTimeOffset(record.LastSeenTime, TimeSpan.Zero), new DateTimeOffset(record.LastSeenTime, TimeSpan.Zero),
IPAddress.Parse(record.LastSeenAddress)); IPAddress.Parse(record.LastSeenAddress),
record.LastSeenHWId?.ToImmutableArray());
} }
private static ServerBanDef? ConvertBan(SqliteServerBan? ban) private static ServerBanDef? ConvertBan(SqliteServerBan? ban)
{ {
if (ban == null) if (ban == null)
@@ -250,6 +258,7 @@ namespace Content.Server.Database
ban.Id, ban.Id,
uid, uid,
addrTuple, addrTuple,
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime, ban.BanTime,
ban.ExpirationTime, ban.ExpirationTime,
ban.Reason, ban.Reason,
@@ -276,7 +285,8 @@ namespace Content.Server.Database
unban.UnbanTime); 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<byte> hwId)
{ {
await using var db = await GetDbImpl(); await using var db = await GetDbImpl();
@@ -285,7 +295,8 @@ namespace Content.Server.Database
Address = address.ToString(), Address = address.ToString(),
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
UserId = userId.UserId, UserId = userId.UserId,
UserName = userName UserName = userName,
HWId = hwId.ToArray()
}); });
await db.SqliteDbContext.SaveChangesAsync(); await db.SqliteDbContext.SaveChangesAsync();