mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Stable to master (#42599)
Ban database refactor (#42495) * Ban DB refactor seems to work at a basic level for PostgreSQL * New ban creation API Supports all the new functionality (multiple players/addresses/hwids/roles/rounds per ban). * Make the migration irreversible * Re-implement ban notifications The server ID check is no longer done as admins may want to place bans spanning multiple rounds irrelevant of the source server. * Fix some split query warnings * Implement migration on SQLite * More comments * Remove required from ban reason SS14.Admin changes would like this * More missing AsSplitQuery() calls * Fix missing ban type filter * Fix old CreateServerBan API with permanent time * Fix department and role ban commands with permanent time * Re-add banhits navigation property Dropped this on accident, SS14.Admin needs it. * More ban API fixes. * Don't fetch ban exemption info for role bans Not relevant, reduces query performance * Regenerate migrations * Fix adminnotes command for players that never connected Would blow up handling null player records. Not a new bug introduced by the refactor, but I ran into it. * Great shame... I accidentally committed submodule update... * Update GDPR scripts * Fix sandbox violation * Fix bans with duplicate info causing DB exceptions Most notably happened with role bans, as multiple departments may include the same role.
This commit is contained in:
committed by
GitHub
parent
facd7da394
commit
29b7fc4463
@@ -1,4 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using Content.Client.Administration.UI.BanList.Bans;
|
using Content.Client.Administration.UI.BanList.Bans;
|
||||||
using Content.Client.Administration.UI.BanList.RoleBans;
|
using Content.Client.Administration.UI.BanList.RoleBans;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
@@ -73,7 +74,7 @@ public sealed class BanListEui : BaseEui
|
|||||||
return date.ToString("MM/dd/yyyy h:mm tt");
|
return date.ToString("MM/dd/yyyy h:mm tt");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetData<T>(IBanListLine<T> line, SharedServerBan ban) where T : SharedServerBan
|
public static void SetData<T>(IBanListLine<T> line, SharedBan ban) where T : SharedBan
|
||||||
{
|
{
|
||||||
line.Reason.Text = ban.Reason;
|
line.Reason.Text = ban.Reason;
|
||||||
line.BanTime.Text = FormatDate(ban.BanTime);
|
line.BanTime.Text = FormatDate(ban.BanTime);
|
||||||
@@ -94,20 +95,20 @@ public sealed class BanListEui : BaseEui
|
|||||||
line.BanningAdmin.Text = ban.BanningAdminName;
|
line.BanningAdmin.Text = ban.BanningAdminName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedServerBan
|
private void OnLineIdsClicked<T>(IBanListLine<T> line) where T : SharedBan
|
||||||
{
|
{
|
||||||
_popup?.Close();
|
_popup?.Close();
|
||||||
_popup = null;
|
_popup = null;
|
||||||
|
|
||||||
var ban = line.Ban;
|
var ban = line.Ban;
|
||||||
var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value));
|
var id = ban.Id == null ? string.Empty : Loc.GetString("ban-list-id", ("id", ban.Id.Value));
|
||||||
var ip = ban.Address == null
|
var ip = ban.Addresses.Length == 0
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: Loc.GetString("ban-list-ip", ("ip", ban.Address.Value.address));
|
: Loc.GetString("ban-list-ip", ("ip", string.Join(',', ban.Addresses.Select(a => a.address))));
|
||||||
var hwid = ban.HWId == null ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", ban.HWId));
|
var hwid = ban.HWIds.Length == 0 ? string.Empty : Loc.GetString("ban-list-hwid", ("hwid", string.Join(',', ban.HWIds)));
|
||||||
var guid = ban.UserId == null
|
var guid = ban.UserIds.Length == 0
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: Loc.GetString("ban-list-guid", ("guid", ban.UserId.Value.ToString()));
|
: Loc.GetString("ban-list-guid", ("guid", string.Join(',', ban.UserIds)));
|
||||||
|
|
||||||
_popup = new BanListIdsPopup(id, ip, hwid, guid);
|
_popup = new BanListIdsPopup(id, ip, hwid, guid);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public sealed partial class BanListControl : Control
|
|||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBans(List<SharedServerBan> bans)
|
public void SetBans(List<SharedBan> bans)
|
||||||
{
|
{
|
||||||
for (var i = Bans.ChildCount - 1; i >= 1; i--)
|
for (var i = Bans.ChildCount - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
|
|||||||
namespace Content.Client.Administration.UI.BanList.Bans;
|
namespace Content.Client.Administration.UI.BanList.Bans;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedServerBan>
|
public sealed partial class BanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||||
{
|
{
|
||||||
public SharedServerBan Ban { get; }
|
public SharedBan Ban { get; }
|
||||||
|
|
||||||
public event Action<BanListLine>? IdsClicked;
|
public event Action<BanListLine>? IdsClicked;
|
||||||
|
|
||||||
public BanListLine(SharedServerBan ban)
|
public BanListLine(SharedBan ban)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Robust.Client.UserInterface.Controls;
|
|||||||
|
|
||||||
namespace Content.Client.Administration.UI.BanList;
|
namespace Content.Client.Administration.UI.BanList;
|
||||||
|
|
||||||
public interface IBanListLine<T> where T : SharedServerBan
|
public interface IBanListLine<T> where T : SharedBan
|
||||||
{
|
{
|
||||||
T Ban { get; }
|
T Ban { get; }
|
||||||
Label Reason { get; }
|
Label Reason { get; }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public sealed partial class RoleBanListControl : Control
|
|||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRoleBans(List<SharedServerRoleBan> bans)
|
public void SetRoleBans(List<SharedBan> bans)
|
||||||
{
|
{
|
||||||
for (var i = RoleBans.ChildCount - 1; i >= 1; i--)
|
for (var i = RoleBans.ChildCount - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ using static Robust.Client.UserInterface.Controls.BaseButton;
|
|||||||
namespace Content.Client.Administration.UI.BanList.RoleBans;
|
namespace Content.Client.Administration.UI.BanList.RoleBans;
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedServerRoleBan>
|
public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedBan>
|
||||||
{
|
{
|
||||||
public SharedServerRoleBan Ban { get; }
|
public SharedBan Ban { get; }
|
||||||
|
|
||||||
public event Action<RoleBanListLine>? IdsClicked;
|
public event Action<RoleBanListLine>? IdsClicked;
|
||||||
|
|
||||||
public RoleBanListLine(SharedServerRoleBan ban)
|
public RoleBanListLine(SharedBan ban)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public sealed partial class RoleBanListLine : BoxContainer, IBanListLine<SharedS
|
|||||||
IdsHidden.OnPressed += IdsPressed;
|
IdsHidden.OnPressed += IdsPressed;
|
||||||
|
|
||||||
BanListEui.SetData(this, ban);
|
BanListEui.SetData(this, ban);
|
||||||
Role.Text = ban.Role;
|
Role.Text = string.Join(", ", ban.Roles ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IdsPressed(ButtonEventArgs buttonEventArgs)
|
private void IdsPressed(ButtonEventArgs buttonEventArgs)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public sealed partial class AdminNotesLine : BoxContainer
|
|||||||
|
|
||||||
TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
ServerLabel.Text = Note.ServerName ?? "Unknown";
|
ServerLabel.Text = Note.ServerName ?? "Unknown";
|
||||||
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
|
RoundLabel.Text = Note.Rounds.Length == 0 ? "Unknown round" : "Round " + string.Join(',', Note.Rounds);
|
||||||
AdminLabel.Text = Note.CreatedByName;
|
AdminLabel.Text = Note.CreatedByName;
|
||||||
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
|
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
|
||||||
|
|
||||||
@@ -143,7 +143,12 @@ public sealed partial class AdminNotesLine : BoxContainer
|
|||||||
|
|
||||||
private string FormatRoleBanMessage()
|
private string FormatRoleBanMessage()
|
||||||
{
|
{
|
||||||
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new[] { "unknown" })} ");
|
var rolesText = string.Join(
|
||||||
|
", ",
|
||||||
|
// Explicit cast here to avoid sandbox violation.
|
||||||
|
(IEnumerable<BanRoleDef>?)Note.BannedRoles ?? [new BanRoleDef("what", "You should not be seeing this")]);
|
||||||
|
|
||||||
|
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {rolesText} ");
|
||||||
return FormatBanMessageCommon(banMessage);
|
return FormatBanMessageCommon(banMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ public sealed partial class AdminNotesLinePopup : Popup
|
|||||||
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
|
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
|
||||||
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
|
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
|
||||||
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
|
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
|
||||||
RoundIdLabel.Text = note.Round == null
|
RoundIdLabel.Text = note.Rounds.Length == 0
|
||||||
? Loc.GetString("admin-notes-round-id-unknown")
|
? Loc.GetString("admin-notes-round-id-unknown")
|
||||||
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
|
: Loc.GetString("admin-notes-round-id", ("id", string.Join(',', note.Rounds)));
|
||||||
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
|
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
|
||||||
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
|
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
|
||||||
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
|
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
|
||||||
private readonly Dictionary<string, TimeSpan> _roles = new();
|
private readonly Dictionary<string, TimeSpan> _roles = new();
|
||||||
private readonly List<string> _jobBans = new();
|
private readonly List<ProtoId<JobPrototype>> _jobBans = new();
|
||||||
private readonly List<string> _antagBans = new();
|
private readonly List<ProtoId<AntagPrototype>> _antagBans = new();
|
||||||
private readonly List<string> _jobWhitelists = new();
|
private readonly List<string> _jobWhitelists = new();
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
// No bans on record
|
// No bans on record
|
||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(1), Is.Null);
|
||||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to pardon a ban that does not exist
|
// Try to pardon a ban that does not exist
|
||||||
@@ -43,9 +43,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
// Still no bans on record
|
// Still no bans on record
|
||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Null);
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(1), Is.Null);
|
||||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Is.Empty);
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Is.Empty);
|
||||||
});
|
});
|
||||||
|
|
||||||
var banReason = "test";
|
var banReason = "test";
|
||||||
@@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
// Should have one ban on record now
|
// Should have one ban on record now
|
||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null);
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(1), Is.Not.Null);
|
Assert.That(await sDatabase.GetBanAsync(1), Is.Not.Null);
|
||||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
await pair.RunTicksSync(5);
|
await pair.RunTicksSync(5);
|
||||||
@@ -70,17 +70,17 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2"));
|
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 2"));
|
||||||
|
|
||||||
// The existing ban is unaffected
|
// The existing ban is unaffected
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Not.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, null), Is.Not.Null);
|
||||||
|
|
||||||
var ban = await sDatabase.GetServerBanAsync(1);
|
var ban = await sDatabase.GetBanAsync(1);
|
||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
Assert.That(ban, Is.Not.Null);
|
Assert.That(ban, Is.Not.Null);
|
||||||
Assert.That(await sDatabase.GetServerBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, 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));
|
||||||
Assert.That(ban.UserId, Is.EqualTo(clientId));
|
Assert.That(ban.UserIds, Is.EquivalentTo([clientId]));
|
||||||
Assert.That(ban.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
|
Assert.That(ban.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
|
||||||
Assert.That(ban.ExpirationTime, Is.Not.Null);
|
Assert.That(ban.ExpirationTime, Is.Not.Null);
|
||||||
Assert.That(ban.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
|
Assert.That(ban.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
|
||||||
@@ -95,20 +95,20 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1"));
|
await server.WaitPost(() => sConsole.ExecuteCommand("pardon 1"));
|
||||||
|
|
||||||
// No bans should be returned
|
// No bans should be returned
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, 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.GetBanAsync(1);
|
||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
// Check that it matches
|
// Check that it matches
|
||||||
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, null, null), Has.Count.EqualTo(1));
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||||
|
|
||||||
Assert.That(pardonedBan.Id, Is.EqualTo(1));
|
Assert.That(pardonedBan.Id, Is.EqualTo(1));
|
||||||
Assert.That(pardonedBan.UserId, Is.EqualTo(clientId));
|
Assert.That(pardonedBan.UserIds, Is.EquivalentTo([clientId]));
|
||||||
Assert.That(pardonedBan.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
|
Assert.That(pardonedBan.BanTime.UtcDateTime - DateTime.UtcNow, Is.LessThanOrEqualTo(MarginOfError));
|
||||||
Assert.That(pardonedBan.ExpirationTime, Is.Not.Null);
|
Assert.That(pardonedBan.ExpirationTime, Is.Not.Null);
|
||||||
Assert.That(pardonedBan.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
|
Assert.That(pardonedBan.ExpirationTime.Value.UtcDateTime - DateTime.UtcNow.AddHours(24), Is.LessThanOrEqualTo(MarginOfError));
|
||||||
@@ -133,13 +133,13 @@ namespace Content.IntegrationTests.Tests.Commands
|
|||||||
Assert.Multiple(async () =>
|
Assert.Multiple(async () =>
|
||||||
{
|
{
|
||||||
// No bans should be returned
|
// No bans should be returned
|
||||||
Assert.That(await sDatabase.GetServerBanAsync(null, clientId, null, null), Is.Null);
|
Assert.That(await sDatabase.GetBanAsync(null, clientId, null, 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.GetBanAsync(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, null, null), Has.Count.EqualTo(1));
|
Assert.That(await sDatabase.GetBansAsync(null, clientId, null, null), Has.Count.EqualTo(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reconnect client. Slightly faster than dirtying the pair.
|
// Reconnect client. Slightly faster than dirtying the pair.
|
||||||
|
|||||||
2125
Content.Server.Database/Migrations/Postgres/20260120200503_BanRefactor.Designer.cs
generated
Normal file
2125
Content.Server.Database/Migrations/Postgres/20260120200503_BanRefactor.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,535 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using NpgsqlTypes;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class BanRefactor : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
type = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
playtime_at_note = table.Column<TimeSpan>(type: "interval", nullable: false),
|
||||||
|
ban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
expiration_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
reason = table.Column<string>(type: "text", nullable: false),
|
||||||
|
severity = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
banning_admin = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
last_edited_by_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
last_edited_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
exempt_flags = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
auto_delete = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
hidden = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban", x => x.ban_id);
|
||||||
|
table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_banning_admin",
|
||||||
|
column: x => x.banning_admin,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_last_edited_by_id",
|
||||||
|
column: x => x.last_edited_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_address",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_address_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
address = table.Column<NpgsqlInet>(type: "inet", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_address", x => x.ban_address_id);
|
||||||
|
table.CheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_address_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_hwid",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_hwid_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
hwid = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
hwid_type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_hwid_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_player",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_player_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_player", x => x.ban_player_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_role",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_role_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
role_type = table.Column<string>(type: "text", nullable: false),
|
||||||
|
role_id = table.Column<string>(type: "text", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_role", x => x.ban_role_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_role_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_round",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_round_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
round_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_round", x => x.ban_round_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_round_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_round_round_round_id",
|
||||||
|
column: x => x.round_id,
|
||||||
|
principalTable: "round",
|
||||||
|
principalColumn: "round_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "unban",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
unban_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
ban_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
unbanning_admin = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
unban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_unban", x => x.unban_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_unban_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_banning_admin",
|
||||||
|
table: "ban",
|
||||||
|
column: "banning_admin");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_last_edited_by_id",
|
||||||
|
table: "ban",
|
||||||
|
column: "last_edited_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_address_ban_id",
|
||||||
|
table: "ban_address",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_hwid_ban_id",
|
||||||
|
table: "ban_hwid",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_player_ban_id",
|
||||||
|
table: "ban_player",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_player_user_id_ban_id",
|
||||||
|
table: "ban_player",
|
||||||
|
columns: new[] { "user_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_role_ban_id",
|
||||||
|
table: "ban_role",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_role_role_type_role_id_ban_id",
|
||||||
|
table: "ban_role",
|
||||||
|
columns: new[] { "role_type", "role_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_round_ban_id",
|
||||||
|
table: "ban_round",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_round_round_id_ban_id",
|
||||||
|
table: "ban_round",
|
||||||
|
columns: new[] { "round_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_unban_ban_id",
|
||||||
|
table: "unban",
|
||||||
|
column: "ban_id",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_server_ban_hit_ban_ban_id",
|
||||||
|
table: "server_ban_hit",
|
||||||
|
column: "ban_id",
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
CREATE INDEX "IX_ban_address_address"
|
||||||
|
ON ban_address
|
||||||
|
USING gist
|
||||||
|
(address inet_ops)
|
||||||
|
INCLUDE (ban_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_ban_hwid_hwid_ban_id"
|
||||||
|
ON ban_hwid
|
||||||
|
(hwid_type, hwid, ban_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_ban_address_address_ban_id"
|
||||||
|
ON ban_address
|
||||||
|
(address, ban_id);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql($"""
|
||||||
|
-- REMOVE:
|
||||||
|
-- TRUNCATE ban RESTART IDENTITY CASCADE;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Insert game bans
|
||||||
|
--
|
||||||
|
INSERT INTO
|
||||||
|
ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
|
||||||
|
SELECT
|
||||||
|
server_ban_id, {(int)BanType.Server}, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden
|
||||||
|
FROM
|
||||||
|
server_ban;
|
||||||
|
|
||||||
|
-- Update ID sequence to be after newly inserted IDs.
|
||||||
|
SELECT setval('ban_ban_id_seq', (SELECT MAX(ban_id) FROM ban));
|
||||||
|
|
||||||
|
-- Insert ban player records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_player (user_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
player_user_id, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
player_user_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban address records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_address (address, ban_id)
|
||||||
|
SELECT
|
||||||
|
address, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
address IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban HWID records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_hwid (hwid, hwid_type, ban_id)
|
||||||
|
SELECT
|
||||||
|
hwid, hwid_type, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
hwid IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban unban records.
|
||||||
|
INSERT INTO
|
||||||
|
unban (ban_id, unbanning_admin, unban_time)
|
||||||
|
SELECT
|
||||||
|
ban_id, unbanning_admin, unban_time
|
||||||
|
FROM server_unban;
|
||||||
|
|
||||||
|
|
||||||
|
-- Insert ban round records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_round (round_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
round_id, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
round_id IS NOT NULL;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Insert role bans
|
||||||
|
-- This shit is a pain in the ass
|
||||||
|
-- > Declarative language
|
||||||
|
-- > Has to write procedural code in it
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Create mapping table from role ban -> server ban.
|
||||||
|
-- We have to manually calculate the new ban IDs by using the sequence.
|
||||||
|
-- We also want to merge role ban records because the game code previously did that in some UI,
|
||||||
|
-- and that code is now gone, expecting the DB to do it.
|
||||||
|
|
||||||
|
-- Create a table to store IDs to merge.
|
||||||
|
CREATE TEMPORARY TABLE /*IF NOT EXISTS*/ _role_ban_import_merge_map (merge_id INTEGER, server_role_ban_id INTEGER UNIQUE) ON COMMIT DROP;
|
||||||
|
-- TRUNCATE _role_ban_import_merge_map;
|
||||||
|
|
||||||
|
-- Create a table to store merged IDs -> new ban IDs
|
||||||
|
CREATE TEMPORARY TABLE /*IF NOT EXISTS*/ _role_ban_import_id_map (ban_id INTEGER UNIQUE, merge_id INTEGER UNIQUE) ON COMMIT DROP;
|
||||||
|
-- TRUNCATE _role_ban_import_id_map;
|
||||||
|
|
||||||
|
-- Calculate merged role bans.
|
||||||
|
INSERT INTO
|
||||||
|
_role_ban_import_merge_map
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
sub.server_role_ban_id
|
||||||
|
FROM
|
||||||
|
server_role_ban AS sub
|
||||||
|
LEFT JOIN server_role_unban AS sub_unban
|
||||||
|
ON sub_unban.ban_id = sub.server_role_ban_id
|
||||||
|
WHERE
|
||||||
|
main.reason IS NOT DISTINCT FROM sub.reason
|
||||||
|
AND main.player_user_id IS NOT DISTINCT FROM sub.player_user_id
|
||||||
|
AND main.address IS NOT DISTINCT FROM sub.address
|
||||||
|
AND main.hwid IS NOT DISTINCT FROM sub.hwid
|
||||||
|
AND main.hwid_type IS NOT DISTINCT FROM sub.hwid_type
|
||||||
|
AND date_trunc('second', main.ban_time, 'utc') = date_trunc('second', sub.ban_time, 'utc')
|
||||||
|
AND (
|
||||||
|
(main.expiration_time IS NULL) = (sub.expiration_time IS NULL)
|
||||||
|
OR date_trunc('minute', main.expiration_time, 'utc') = date_trunc('minute', sub.expiration_time, 'utc')
|
||||||
|
)
|
||||||
|
AND main.round_id IS NOT DISTINCT FROM sub.round_id
|
||||||
|
AND main.severity IS NOT DISTINCT FROM sub.severity
|
||||||
|
AND main.hidden IS NOT DISTINCT FROM sub.hidden
|
||||||
|
AND main.banning_admin IS NOT DISTINCT FROM sub.banning_admin
|
||||||
|
AND (sub_unban.ban_id IS NULL) = (main_unban.ban_id IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
sub.server_role_ban_id ASC
|
||||||
|
LIMIT 1
|
||||||
|
), main.server_role_ban_id
|
||||||
|
FROM
|
||||||
|
server_role_ban AS main
|
||||||
|
LEFT JOIN server_role_unban AS main_unban
|
||||||
|
ON main_unban.ban_id = main.server_role_ban_id;
|
||||||
|
|
||||||
|
-- Assign new ban IDs for merged IDs.
|
||||||
|
INSERT INTO
|
||||||
|
_role_ban_import_id_map
|
||||||
|
SELECT
|
||||||
|
DISTINCT ON (merge_id)
|
||||||
|
nextval('ban_ban_id_seq'),
|
||||||
|
merge_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_merge_map;
|
||||||
|
|
||||||
|
-- I sure fucking wish CTEs could span multiple queries...
|
||||||
|
|
||||||
|
-- Insert new ban records
|
||||||
|
INSERT INTO
|
||||||
|
ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
|
||||||
|
SELECT
|
||||||
|
im.ban_id, {(int)BanType.Role}, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, 0, FALSE, hidden
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id;
|
||||||
|
|
||||||
|
-- Insert role ban player records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_player (user_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
player_user_id, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND player_user_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban address records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_address (address, ban_id)
|
||||||
|
SELECT
|
||||||
|
address, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND address IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban HWID records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_hwid (hwid, hwid_type, ban_id)
|
||||||
|
SELECT
|
||||||
|
hwid, hwid_type, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND hwid IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban role records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_role (role_type, role_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
split_part(role_id, ':', 1), split_part(role_id, ':', 2), im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = mm.server_role_ban_id
|
||||||
|
-- Yes, we have some messy ban records which, after merging, end up with duplicate roles.
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Insert role unban records.
|
||||||
|
INSERT INTO
|
||||||
|
unban (ban_id, unbanning_admin, unban_time)
|
||||||
|
SELECT
|
||||||
|
im.ban_id, unbanning_admin, unban_time
|
||||||
|
FROM server_role_unban sru
|
||||||
|
INNER JOIN _role_ban_import_id_map im
|
||||||
|
ON im.merge_id = sru.ban_id;
|
||||||
|
|
||||||
|
-- Insert role rounds
|
||||||
|
INSERT INTO
|
||||||
|
ban_round (round_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
round_id, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND round_id IS NOT NULL;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_server_ban_hit_server_ban_ban_id",
|
||||||
|
table: "server_ban_hit");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_role_unban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_unban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_role_ban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_ban");
|
||||||
|
|
||||||
|
migrationBuilder.Sql($"""
|
||||||
|
CREATE OR REPLACE FUNCTION send_server_ban_notification()
|
||||||
|
RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
PERFORM pg_notify(
|
||||||
|
'ban_notification',
|
||||||
|
json_build_object('ban_id', NEW.ban_id)::text
|
||||||
|
);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER notify_on_server_ban_insert
|
||||||
|
AFTER INSERT ON ban
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.type = {(int)BanType.Server})
|
||||||
|
EXECUTE FUNCTION send_server_ban_notification();
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This migration cannot be rolled back");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -519,6 +519,221 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("assigned_user_id", (string)null);
|
b.ToTable("assigned_user_id", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<bool>("AutoDelete")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("auto_delete");
|
||||||
|
|
||||||
|
b.Property<DateTime>("BanTime")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("ban_time");
|
||||||
|
|
||||||
|
b.Property<Guid?>("BanningAdmin")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("banning_admin");
|
||||||
|
|
||||||
|
b.Property<int>("ExemptFlags")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("exempt_flags");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
|
b.Property<bool>("Hidden")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("hidden");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastEditedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastEditedById")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("last_edited_by_id");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("PlaytimeAtNote")
|
||||||
|
.HasColumnType("interval")
|
||||||
|
.HasColumnName("playtime_at_note");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<int>("Severity")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("severity");
|
||||||
|
|
||||||
|
b.Property<byte>("Type")
|
||||||
|
.HasColumnType("smallint")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban");
|
||||||
|
|
||||||
|
b.HasIndex("BanningAdmin");
|
||||||
|
|
||||||
|
b.HasIndex("LastEditedById");
|
||||||
|
|
||||||
|
b.ToTable("ban", null, t =>
|
||||||
|
{
|
||||||
|
t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_address_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<NpgsqlInet>("Address")
|
||||||
|
.HasColumnType("inet")
|
||||||
|
.HasColumnName("address");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_address");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_address_ban_id");
|
||||||
|
|
||||||
|
b.ToTable("ban_address", null, t =>
|
||||||
|
{
|
||||||
|
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_hwid_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_hwid");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_hwid_ban_id");
|
||||||
|
|
||||||
|
b.ToTable("ban_hwid", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_player_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("user_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_player");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_player_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_player", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_role_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("role_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("role_type");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_role");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_role_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleType", "RoleId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_role", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRound", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_round_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<int>("RoundId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("round_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_round");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_round_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoundId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_round", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1069,95 +1284,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("server", (string)null);
|
b.ToTable("server", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("server_ban_id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<NpgsqlInet?>("Address")
|
|
||||||
.HasColumnType("inet")
|
|
||||||
.HasColumnName("address");
|
|
||||||
|
|
||||||
b.Property<bool>("AutoDelete")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("auto_delete");
|
|
||||||
|
|
||||||
b.Property<DateTime>("BanTime")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("ban_time");
|
|
||||||
|
|
||||||
b.Property<Guid?>("BanningAdmin")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("banning_admin");
|
|
||||||
|
|
||||||
b.Property<int>("ExemptFlags")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("exempt_flags");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("ExpirationTime")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("expiration_time");
|
|
||||||
|
|
||||||
b.Property<bool>("Hidden")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("hidden");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("last_edited_at");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastEditedById")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("last_edited_by_id");
|
|
||||||
|
|
||||||
b.Property<Guid?>("PlayerUserId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("player_user_id");
|
|
||||||
|
|
||||||
b.Property<TimeSpan>("PlaytimeAtNote")
|
|
||||||
.HasColumnType("interval")
|
|
||||||
.HasColumnName("playtime_at_note");
|
|
||||||
|
|
||||||
b.Property<string>("Reason")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("reason");
|
|
||||||
|
|
||||||
b.Property<int?>("RoundId")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("round_id");
|
|
||||||
|
|
||||||
b.Property<int>("Severity")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("severity");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("PK_server_ban");
|
|
||||||
|
|
||||||
b.HasIndex("Address");
|
|
||||||
|
|
||||||
b.HasIndex("BanningAdmin");
|
|
||||||
|
|
||||||
b.HasIndex("LastEditedById");
|
|
||||||
|
|
||||||
b.HasIndex("PlayerUserId")
|
|
||||||
.HasDatabaseName("IX_server_ban_player_user_id");
|
|
||||||
|
|
||||||
b.HasIndex("RoundId")
|
|
||||||
.HasDatabaseName("IX_server_ban_round_id");
|
|
||||||
|
|
||||||
b.ToTable("server_ban", null, t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
|
|
||||||
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
|
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("UserId")
|
b.Property<Guid>("UserId")
|
||||||
@@ -1207,152 +1333,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("server_ban_hit", (string)null);
|
b.ToTable("server_ban_hit", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("server_role_ban_id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<NpgsqlInet?>("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<bool>("Hidden")
|
|
||||||
.HasColumnType("boolean")
|
|
||||||
.HasColumnName("hidden");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("last_edited_at");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastEditedById")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("last_edited_by_id");
|
|
||||||
|
|
||||||
b.Property<Guid?>("PlayerUserId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("player_user_id");
|
|
||||||
|
|
||||||
b.Property<TimeSpan>("PlaytimeAtNote")
|
|
||||||
.HasColumnType("interval")
|
|
||||||
.HasColumnName("playtime_at_note");
|
|
||||||
|
|
||||||
b.Property<string>("Reason")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("reason");
|
|
||||||
|
|
||||||
b.Property<string>("RoleId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("role_id");
|
|
||||||
|
|
||||||
b.Property<int?>("RoundId")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("round_id");
|
|
||||||
|
|
||||||
b.Property<int>("Severity")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("severity");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("PK_server_role_ban");
|
|
||||||
|
|
||||||
b.HasIndex("Address");
|
|
||||||
|
|
||||||
b.HasIndex("BanningAdmin");
|
|
||||||
|
|
||||||
b.HasIndex("LastEditedById");
|
|
||||||
|
|
||||||
b.HasIndex("PlayerUserId")
|
|
||||||
.HasDatabaseName("IX_server_role_ban_player_user_id");
|
|
||||||
|
|
||||||
b.HasIndex("RoundId")
|
|
||||||
.HasDatabaseName("IX_server_role_ban_round_id");
|
|
||||||
|
|
||||||
b.ToTable("server_role_ban", null, t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
|
|
||||||
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("role_unban_id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
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")
|
|
||||||
.HasName("PK_server_role_unban");
|
|
||||||
|
|
||||||
b.HasIndex("BanId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("server_role_unban", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("unban_id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
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")
|
|
||||||
.HasName("PK_server_unban");
|
|
||||||
|
|
||||||
b.HasIndex("BanId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("server_unban", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1380,6 +1360,36 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("trait", (string)null);
|
b.ToTable("trait", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Unban", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("unban_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
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")
|
||||||
|
.HasName("PK_unban");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("unban", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1664,6 +1674,123 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
||||||
|
.WithMany("AdminServerBansCreated")
|
||||||
|
.HasForeignKey("BanningAdmin")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.HasConstraintName("FK_ban_player_banning_admin");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
||||||
|
.WithMany("AdminServerBansLastEdited")
|
||||||
|
.HasForeignKey("LastEditedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.HasConstraintName("FK_ban_player_last_edited_by_id");
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("LastEditedBy");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_address_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Hwids")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_hwid_ban_ban_id");
|
||||||
|
|
||||||
|
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("BanHwidId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("ban_hwid_id");
|
||||||
|
|
||||||
|
b1.Property<byte[]>("Hwid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("hwid");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("hwid_type");
|
||||||
|
|
||||||
|
b1.HasKey("BanHwidId");
|
||||||
|
|
||||||
|
b1.ToTable("ban_hwid");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("BanHwidId")
|
||||||
|
.HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
|
||||||
|
b.Navigation("HWId")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Players")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_player_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_role_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRound", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Rounds")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_round_ban_ban_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Round", "Round")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoundId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_round_round_round_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
|
||||||
|
b.Navigation("Round");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Server", "Server")
|
b.HasOne("Content.Server.Database.Server", "Server")
|
||||||
@@ -1820,70 +1947,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Server");
|
b.Navigation("Server");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
|
||||||
.WithMany("AdminServerBansCreated")
|
|
||||||
.HasForeignKey("BanningAdmin")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_ban_player_banning_admin");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
|
||||||
.WithMany("AdminServerBansLastEdited")
|
|
||||||
.HasForeignKey("LastEditedById")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_ban_player_last_edited_by_id");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Round", "Round")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoundId")
|
|
||||||
.HasConstraintName("FK_server_ban_round_round_id");
|
|
||||||
|
|
||||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
|
||||||
{
|
|
||||||
b1.Property<int>("ServerBanId")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("server_ban_id");
|
|
||||||
|
|
||||||
b1.Property<byte[]>("Hwid")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("bytea")
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
b1.Property<int>("Type")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("hwid_type");
|
|
||||||
|
|
||||||
b1.HasKey("ServerBanId");
|
|
||||||
|
|
||||||
b1.ToTable("server_ban");
|
|
||||||
|
|
||||||
b1.WithOwner()
|
|
||||||
.HasForeignKey("ServerBanId")
|
|
||||||
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.Navigation("CreatedBy");
|
|
||||||
|
|
||||||
b.Navigation("HWId");
|
|
||||||
|
|
||||||
b.Navigation("LastEditedBy");
|
|
||||||
|
|
||||||
b.Navigation("Round");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
|
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.ServerBan", "Ban")
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
.WithMany("BanHits")
|
.WithMany("BanHits")
|
||||||
.HasForeignKey("BanId")
|
.HasForeignKey("BanId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
|
.HasConstraintName("FK_server_ban_hit_ban_ban_id");
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
|
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
|
||||||
.WithMany("BanHits")
|
.WithMany("BanHits")
|
||||||
@@ -1897,86 +1968,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Connection");
|
b.Navigation("Connection");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
|
||||||
.WithMany("AdminServerRoleBansCreated")
|
|
||||||
.HasForeignKey("BanningAdmin")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_role_ban_player_banning_admin");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
|
||||||
.WithMany("AdminServerRoleBansLastEdited")
|
|
||||||
.HasForeignKey("LastEditedById")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Round", "Round")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoundId")
|
|
||||||
.HasConstraintName("FK_server_role_ban_round_round_id");
|
|
||||||
|
|
||||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
|
||||||
{
|
|
||||||
b1.Property<int>("ServerRoleBanId")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("server_role_ban_id");
|
|
||||||
|
|
||||||
b1.Property<byte[]>("Hwid")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("bytea")
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
b1.Property<int>("Type")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("hwid_type");
|
|
||||||
|
|
||||||
b1.HasKey("ServerRoleBanId");
|
|
||||||
|
|
||||||
b1.ToTable("server_role_ban");
|
|
||||||
|
|
||||||
b1.WithOwner()
|
|
||||||
.HasForeignKey("ServerRoleBanId")
|
|
||||||
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.Navigation("CreatedBy");
|
|
||||||
|
|
||||||
b.Navigation("HWId");
|
|
||||||
|
|
||||||
b.Navigation("LastEditedBy");
|
|
||||||
|
|
||||||
b.Navigation("Round");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
|
|
||||||
.WithOne("Unban")
|
|
||||||
.HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
|
|
||||||
|
|
||||||
b.Navigation("Ban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.ServerBan", "Ban")
|
|
||||||
.WithOne("Unban")
|
|
||||||
.HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("FK_server_unban_server_ban_ban_id");
|
|
||||||
|
|
||||||
b.Navigation("Ban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||||
@@ -1989,6 +1980,18 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Unban", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithOne("Unban")
|
||||||
|
.HasForeignKey("Content.Server.Database.Unban", "BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_unban_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlayerRound", b =>
|
modelBuilder.Entity("PlayerRound", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Player", null)
|
b.HasOne("Content.Server.Database.Player", null)
|
||||||
@@ -2023,6 +2026,23 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Flags");
|
b.Navigation("Flags");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("BanHits");
|
||||||
|
|
||||||
|
b.Navigation("Hwids");
|
||||||
|
|
||||||
|
b.Navigation("Players");
|
||||||
|
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("Rounds");
|
||||||
|
|
||||||
|
b.Navigation("Unban");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("BanHits");
|
b.Navigation("BanHits");
|
||||||
@@ -2052,10 +2072,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
|
|
||||||
b.Navigation("AdminServerBansLastEdited");
|
b.Navigation("AdminServerBansLastEdited");
|
||||||
|
|
||||||
b.Navigation("AdminServerRoleBansCreated");
|
|
||||||
|
|
||||||
b.Navigation("AdminServerRoleBansLastEdited");
|
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsCreated");
|
b.Navigation("AdminWatchlistsCreated");
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsDeleted");
|
b.Navigation("AdminWatchlistsDeleted");
|
||||||
@@ -2104,18 +2120,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
|
|
||||||
b.Navigation("Rounds");
|
b.Navigation("Rounds");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("BanHits");
|
|
||||||
|
|
||||||
b.Navigation("Unban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Unban");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2044
Content.Server.Database/Migrations/Sqlite/20260120200455_BanRefactor.Designer.cs
generated
Normal file
2044
Content.Server.Database/Migrations/Sqlite/20260120200455_BanRefactor.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,498 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class BanRefactor : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
type = table.Column<byte>(type: "INTEGER", nullable: false),
|
||||||
|
playtime_at_note = table.Column<TimeSpan>(type: "TEXT", nullable: false),
|
||||||
|
ban_time = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
expiration_time = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
|
reason = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
severity = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
banning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||||
|
last_edited_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||||
|
last_edited_at = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
|
exempt_flags = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
auto_delete = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
hidden = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban", x => x.ban_id);
|
||||||
|
table.CheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_banning_admin",
|
||||||
|
column: x => x.banning_admin,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_last_edited_by_id",
|
||||||
|
column: x => x.last_edited_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_address",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_address_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
address = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_address", x => x.ban_address_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_address_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_hwid",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_hwid_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
hwid = table.Column<byte[]>(type: "BLOB", nullable: false),
|
||||||
|
hwid_type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_hwid", x => x.ban_hwid_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_hwid_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_player",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_player_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_player", x => x.ban_player_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_player_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_role",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_role_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
role_type = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
role_id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_role", x => x.ban_role_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_role_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ban_round",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ban_round_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
round_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ban_round", x => x.ban_round_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_round_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ban_round_round_round_id",
|
||||||
|
column: x => x.round_id,
|
||||||
|
principalTable: "round",
|
||||||
|
principalColumn: "round_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "unban",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
unban_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
ban_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
unbanning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||||
|
unban_time = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_unban", x => x.unban_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_unban_ban_ban_id",
|
||||||
|
column: x => x.ban_id,
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_banning_admin",
|
||||||
|
table: "ban",
|
||||||
|
column: "banning_admin");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_last_edited_by_id",
|
||||||
|
table: "ban",
|
||||||
|
column: "last_edited_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_address_ban_id",
|
||||||
|
table: "ban_address",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_hwid_ban_id",
|
||||||
|
table: "ban_hwid",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_player_ban_id",
|
||||||
|
table: "ban_player",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_player_user_id_ban_id",
|
||||||
|
table: "ban_player",
|
||||||
|
columns: new[] { "user_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_role_ban_id",
|
||||||
|
table: "ban_role",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_role_role_type_role_id_ban_id",
|
||||||
|
table: "ban_role",
|
||||||
|
columns: new[] { "role_type", "role_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_round_ban_id",
|
||||||
|
table: "ban_round",
|
||||||
|
column: "ban_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ban_round_round_id_ban_id",
|
||||||
|
table: "ban_round",
|
||||||
|
columns: new[] { "round_id", "ban_id" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_unban_ban_id",
|
||||||
|
table: "unban",
|
||||||
|
column: "ban_id",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
CREATE UNIQUE INDEX "IX_ban_hwid_hwid_ban_id"
|
||||||
|
ON ban_hwid
|
||||||
|
(hwid_type, hwid, ban_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_ban_address_address_ban_id"
|
||||||
|
ON ban_address
|
||||||
|
(address, ban_id);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
--
|
||||||
|
-- Insert game bans
|
||||||
|
--
|
||||||
|
INSERT INTO
|
||||||
|
ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
|
||||||
|
SELECT
|
||||||
|
server_ban_id, 0, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden
|
||||||
|
FROM
|
||||||
|
server_ban;
|
||||||
|
|
||||||
|
-- Insert ban player records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_player (user_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
player_user_id, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
player_user_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban address records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_address (address, ban_id)
|
||||||
|
SELECT
|
||||||
|
address, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
address IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban HWID records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_hwid (hwid, hwid_type, ban_id)
|
||||||
|
SELECT
|
||||||
|
hwid, hwid_type, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
hwid IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert ban unban records.
|
||||||
|
INSERT INTO
|
||||||
|
unban (ban_id, unbanning_admin, unban_time)
|
||||||
|
SELECT
|
||||||
|
ban_id, unbanning_admin, unban_time
|
||||||
|
FROM server_unban;
|
||||||
|
|
||||||
|
-- Insert ban round records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_round (round_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
round_id, server_ban_id
|
||||||
|
FROM
|
||||||
|
server_ban
|
||||||
|
WHERE
|
||||||
|
round_id IS NOT NULL;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Insert role bans
|
||||||
|
-- This shit is a pain in the ass
|
||||||
|
-- > Declarative language
|
||||||
|
-- > Has to write procedural code in it
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Create mapping table from role ban -> server ban.
|
||||||
|
-- We have to manually calculate the new ban IDs by using the sequence.
|
||||||
|
-- We also want to merge role ban records because the game code previously did that in some UI,
|
||||||
|
-- and that code is now gone, expecting the DB to do it.
|
||||||
|
|
||||||
|
-- Create a table to store IDs to merge.
|
||||||
|
CREATE TEMPORARY TABLE _role_ban_import_merge_map (merge_id INTEGER, server_role_ban_id INTEGER UNIQUE);
|
||||||
|
|
||||||
|
-- Create a table to store merged IDs -> new ban IDs
|
||||||
|
CREATE TEMPORARY TABLE _role_ban_import_id_map (ban_id INTEGER UNIQUE, merge_id INTEGER UNIQUE);
|
||||||
|
|
||||||
|
-- Calculate merged role bans.
|
||||||
|
INSERT INTO
|
||||||
|
_role_ban_import_merge_map
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
sub.server_role_ban_id
|
||||||
|
FROM
|
||||||
|
server_role_ban AS sub
|
||||||
|
LEFT JOIN server_role_unban AS sub_unban
|
||||||
|
ON sub_unban.ban_id = sub.server_role_ban_id
|
||||||
|
WHERE
|
||||||
|
main.reason IS NOT DISTINCT FROM sub.reason
|
||||||
|
AND main.player_user_id IS NOT DISTINCT FROM sub.player_user_id
|
||||||
|
AND main.address IS NOT DISTINCT FROM sub.address
|
||||||
|
AND main.hwid IS NOT DISTINCT FROM sub.hwid
|
||||||
|
AND main.hwid_type IS NOT DISTINCT FROM sub.hwid_type
|
||||||
|
AND main.ban_time = sub.ban_time
|
||||||
|
AND (
|
||||||
|
(main.expiration_time IS NULL) = (sub.expiration_time IS NULL)
|
||||||
|
OR main.expiration_time = sub.expiration_time
|
||||||
|
)
|
||||||
|
AND main.round_id IS NOT DISTINCT FROM sub.round_id
|
||||||
|
AND main.severity IS NOT DISTINCT FROM sub.severity
|
||||||
|
AND main.hidden IS NOT DISTINCT FROM sub.hidden
|
||||||
|
AND main.banning_admin IS NOT DISTINCT FROM sub.banning_admin
|
||||||
|
AND (sub_unban.ban_id IS NULL) = (main_unban.ban_id IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
sub.server_role_ban_id ASC
|
||||||
|
LIMIT 1
|
||||||
|
), main.server_role_ban_id
|
||||||
|
FROM
|
||||||
|
server_role_ban AS main
|
||||||
|
LEFT JOIN server_role_unban AS main_unban
|
||||||
|
ON main_unban.ban_id = main.server_role_ban_id;
|
||||||
|
|
||||||
|
-- Assign new ban IDs for merged IDs.
|
||||||
|
INSERT OR IGNORE INTO
|
||||||
|
_role_ban_import_id_map
|
||||||
|
SELECT
|
||||||
|
merge_id + (SELECT seq FROM sqlite_sequence WHERE name = 'ban'),
|
||||||
|
merge_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_merge_map;
|
||||||
|
|
||||||
|
-- I sure fucking wish CTEs could span multiple queries...
|
||||||
|
|
||||||
|
-- Insert new ban records
|
||||||
|
INSERT INTO
|
||||||
|
ban (ban_id, type, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, exempt_flags, auto_delete, hidden)
|
||||||
|
SELECT
|
||||||
|
im.ban_id, 1, playtime_at_note, ban_time, expiration_time, reason, severity, banning_admin, last_edited_by_id, last_edited_at, 0, FALSE, hidden
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id;
|
||||||
|
|
||||||
|
-- Insert role ban player records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_player (user_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
player_user_id, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND player_user_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban address records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_address (address, ban_id)
|
||||||
|
SELECT
|
||||||
|
address, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND address IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban HWID records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_hwid (hwid, hwid_type, ban_id)
|
||||||
|
SELECT
|
||||||
|
hwid, hwid_type, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND hwid IS NOT NULL;
|
||||||
|
|
||||||
|
-- Insert role ban role records.
|
||||||
|
INSERT INTO
|
||||||
|
ban_role (role_type, role_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
substr(role_id, 1, instr(role_id, ':')-1),
|
||||||
|
substr(role_id, instr(role_id, ':')+1),
|
||||||
|
im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = mm.server_role_ban_id
|
||||||
|
-- Yes, we have some messy ban records which, after merging, end up with duplicate roles.
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Insert role unban records.
|
||||||
|
INSERT INTO
|
||||||
|
unban (ban_id, unbanning_admin, unban_time)
|
||||||
|
SELECT
|
||||||
|
im.ban_id, unbanning_admin, unban_time
|
||||||
|
FROM server_role_unban sru
|
||||||
|
INNER JOIN _role_ban_import_id_map im
|
||||||
|
ON im.merge_id = sru.ban_id;
|
||||||
|
|
||||||
|
-- Insert role rounds
|
||||||
|
INSERT INTO
|
||||||
|
ban_round (round_id, ban_id)
|
||||||
|
SELECT
|
||||||
|
round_id, im.ban_id
|
||||||
|
FROM
|
||||||
|
_role_ban_import_id_map im
|
||||||
|
INNER JOIN _role_ban_import_merge_map mm
|
||||||
|
ON im.merge_id = mm.merge_id
|
||||||
|
INNER JOIN server_role_ban srb
|
||||||
|
ON srb.server_role_ban_id = im.merge_id
|
||||||
|
WHERE mm.merge_id = mm.server_role_ban_id
|
||||||
|
AND round_id IS NOT NULL;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_server_ban_hit_ban_ban_id",
|
||||||
|
table: "server_ban_hit",
|
||||||
|
column: "ban_id",
|
||||||
|
principalTable: "ban",
|
||||||
|
principalColumn: "ban_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_server_ban_hit_server_ban_ban_id",
|
||||||
|
table: "server_ban_hit");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_role_unban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_unban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_role_ban");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "server_ban");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This migration cannot be rolled back");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -489,6 +489,207 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("assigned_user_id", (string)null);
|
b.ToTable("assigned_user_id", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoDelete")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("auto_delete");
|
||||||
|
|
||||||
|
b.Property<DateTime>("BanTime")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("ban_time");
|
||||||
|
|
||||||
|
b.Property<Guid?>("BanningAdmin")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("banning_admin");
|
||||||
|
|
||||||
|
b.Property<int>("ExemptFlags")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("exempt_flags");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("expiration_time");
|
||||||
|
|
||||||
|
b.Property<bool>("Hidden")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("hidden");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastEditedAt")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_edited_at");
|
||||||
|
|
||||||
|
b.Property<Guid?>("LastEditedById")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("last_edited_by_id");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("PlaytimeAtNote")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("playtime_at_note");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("reason");
|
||||||
|
|
||||||
|
b.Property<int>("Severity")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("severity");
|
||||||
|
|
||||||
|
b.Property<byte>("Type")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban");
|
||||||
|
|
||||||
|
b.HasIndex("BanningAdmin");
|
||||||
|
|
||||||
|
b.HasIndex("LastEditedById");
|
||||||
|
|
||||||
|
b.ToTable("ban", null, t =>
|
||||||
|
{
|
||||||
|
t.HasCheckConstraint("NoExemptOnRoleBan", "type = 0 OR exempt_flags = 0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_address_id");
|
||||||
|
|
||||||
|
b.Property<string>("Address")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("address");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_address");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_address_ban_id");
|
||||||
|
|
||||||
|
b.ToTable("ban_address", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_hwid_id");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_hwid");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_hwid_ban_id");
|
||||||
|
|
||||||
|
b.ToTable("ban_hwid", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_player_id");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("user_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_player");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_player_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_player", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_role_id");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("role_id");
|
||||||
|
|
||||||
|
b.Property<string>("RoleType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("role_type");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_role");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_role_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleType", "RoleId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_role", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRound", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_round_id");
|
||||||
|
|
||||||
|
b.Property<int>("BanId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_id");
|
||||||
|
|
||||||
|
b.Property<int>("RoundId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("round_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_ban_round");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.HasDatabaseName("IX_ban_round_ban_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoundId", "BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ban_round", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1010,91 +1211,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("server", (string)null);
|
b.ToTable("server", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("server_ban_id");
|
|
||||||
|
|
||||||
b.Property<string>("Address")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("address");
|
|
||||||
|
|
||||||
b.Property<bool>("AutoDelete")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("auto_delete");
|
|
||||||
|
|
||||||
b.Property<DateTime>("BanTime")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("ban_time");
|
|
||||||
|
|
||||||
b.Property<Guid?>("BanningAdmin")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("banning_admin");
|
|
||||||
|
|
||||||
b.Property<int>("ExemptFlags")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("exempt_flags");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("ExpirationTime")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("expiration_time");
|
|
||||||
|
|
||||||
b.Property<bool>("Hidden")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("hidden");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("last_edited_at");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastEditedById")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("last_edited_by_id");
|
|
||||||
|
|
||||||
b.Property<Guid?>("PlayerUserId")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("player_user_id");
|
|
||||||
|
|
||||||
b.Property<TimeSpan>("PlaytimeAtNote")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("playtime_at_note");
|
|
||||||
|
|
||||||
b.Property<string>("Reason")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("reason");
|
|
||||||
|
|
||||||
b.Property<int?>("RoundId")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("round_id");
|
|
||||||
|
|
||||||
b.Property<int>("Severity")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("severity");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("PK_server_ban");
|
|
||||||
|
|
||||||
b.HasIndex("Address");
|
|
||||||
|
|
||||||
b.HasIndex("BanningAdmin");
|
|
||||||
|
|
||||||
b.HasIndex("LastEditedById");
|
|
||||||
|
|
||||||
b.HasIndex("PlayerUserId")
|
|
||||||
.HasDatabaseName("IX_server_ban_player_user_id");
|
|
||||||
|
|
||||||
b.HasIndex("RoundId")
|
|
||||||
.HasDatabaseName("IX_server_ban_round_id");
|
|
||||||
|
|
||||||
b.ToTable("server_ban", null, t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
|
modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("UserId")
|
b.Property<Guid>("UserId")
|
||||||
@@ -1142,144 +1258,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("server_ban_hit", (string)null);
|
b.ToTable("server_ban_hit", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("server_role_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<bool>("Hidden")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("hidden");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastEditedAt")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("last_edited_at");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastEditedById")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("last_edited_by_id");
|
|
||||||
|
|
||||||
b.Property<Guid?>("PlayerUserId")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("player_user_id");
|
|
||||||
|
|
||||||
b.Property<TimeSpan>("PlaytimeAtNote")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("playtime_at_note");
|
|
||||||
|
|
||||||
b.Property<string>("Reason")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("reason");
|
|
||||||
|
|
||||||
b.Property<string>("RoleId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasColumnName("role_id");
|
|
||||||
|
|
||||||
b.Property<int?>("RoundId")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("round_id");
|
|
||||||
|
|
||||||
b.Property<int>("Severity")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("severity");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("PK_server_role_ban");
|
|
||||||
|
|
||||||
b.HasIndex("Address");
|
|
||||||
|
|
||||||
b.HasIndex("BanningAdmin");
|
|
||||||
|
|
||||||
b.HasIndex("LastEditedById");
|
|
||||||
|
|
||||||
b.HasIndex("PlayerUserId")
|
|
||||||
.HasDatabaseName("IX_server_role_ban_player_user_id");
|
|
||||||
|
|
||||||
b.HasIndex("RoundId")
|
|
||||||
.HasDatabaseName("IX_server_role_ban_round_id");
|
|
||||||
|
|
||||||
b.ToTable("server_role_ban", null, t =>
|
|
||||||
{
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("role_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")
|
|
||||||
.HasName("PK_server_role_unban");
|
|
||||||
|
|
||||||
b.HasIndex("BanId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("server_role_unban", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerUnban", 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")
|
|
||||||
.HasName("PK_server_unban");
|
|
||||||
|
|
||||||
b.HasIndex("BanId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("server_unban", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1305,6 +1283,34 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("trait", (string)null);
|
b.ToTable("trait", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Unban", 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")
|
||||||
|
.HasName("PK_unban");
|
||||||
|
|
||||||
|
b.HasIndex("BanId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("unban", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1587,6 +1593,123 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
||||||
|
.WithMany("AdminServerBansCreated")
|
||||||
|
.HasForeignKey("BanningAdmin")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.HasConstraintName("FK_ban_player_banning_admin");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
||||||
|
.WithMany("AdminServerBansLastEdited")
|
||||||
|
.HasForeignKey("LastEditedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.HasConstraintName("FK_ban_player_last_edited_by_id");
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("LastEditedBy");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanAddress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Addresses")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_address_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanHwid", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Hwids")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_hwid_ban_ban_id");
|
||||||
|
|
||||||
|
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("BanHwidId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("ban_hwid_id");
|
||||||
|
|
||||||
|
b1.Property<byte[]>("Hwid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("BLOB")
|
||||||
|
.HasColumnName("hwid");
|
||||||
|
|
||||||
|
b1.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("hwid_type");
|
||||||
|
|
||||||
|
b1.HasKey("BanHwidId");
|
||||||
|
|
||||||
|
b1.ToTable("ban_hwid");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("BanHwidId")
|
||||||
|
.HasConstraintName("FK_ban_hwid_ban_hwid_ban_hwid_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
|
||||||
|
b.Navigation("HWId")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanPlayer", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Players")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_player_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_role_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.BanRound", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithMany("Rounds")
|
||||||
|
.HasForeignKey("BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_round_ban_ban_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Round", "Round")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoundId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_ban_round_round_round_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
|
||||||
|
b.Navigation("Round");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Server", "Server")
|
b.HasOne("Content.Server.Database.Server", "Server")
|
||||||
@@ -1743,70 +1866,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Server");
|
b.Navigation("Server");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
|
||||||
.WithMany("AdminServerBansCreated")
|
|
||||||
.HasForeignKey("BanningAdmin")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_ban_player_banning_admin");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
|
||||||
.WithMany("AdminServerBansLastEdited")
|
|
||||||
.HasForeignKey("LastEditedById")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_ban_player_last_edited_by_id");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Round", "Round")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoundId")
|
|
||||||
.HasConstraintName("FK_server_ban_round_round_id");
|
|
||||||
|
|
||||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
|
||||||
{
|
|
||||||
b1.Property<int>("ServerBanId")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("server_ban_id");
|
|
||||||
|
|
||||||
b1.Property<byte[]>("Hwid")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("BLOB")
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
b1.Property<int>("Type")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("hwid_type");
|
|
||||||
|
|
||||||
b1.HasKey("ServerBanId");
|
|
||||||
|
|
||||||
b1.ToTable("server_ban");
|
|
||||||
|
|
||||||
b1.WithOwner()
|
|
||||||
.HasForeignKey("ServerBanId")
|
|
||||||
.HasConstraintName("FK_server_ban_server_ban_server_ban_id");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.Navigation("CreatedBy");
|
|
||||||
|
|
||||||
b.Navigation("HWId");
|
|
||||||
|
|
||||||
b.Navigation("LastEditedBy");
|
|
||||||
|
|
||||||
b.Navigation("Round");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
|
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.ServerBan", "Ban")
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
.WithMany("BanHits")
|
.WithMany("BanHits")
|
||||||
.HasForeignKey("BanId")
|
.HasForeignKey("BanId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
|
.HasConstraintName("FK_server_ban_hit_ban_ban_id");
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
|
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
|
||||||
.WithMany("BanHits")
|
.WithMany("BanHits")
|
||||||
@@ -1820,86 +1887,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Connection");
|
b.Navigation("Connection");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
|
||||||
.WithMany("AdminServerRoleBansCreated")
|
|
||||||
.HasForeignKey("BanningAdmin")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_role_ban_player_banning_admin");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
|
|
||||||
.WithMany("AdminServerRoleBansLastEdited")
|
|
||||||
.HasForeignKey("LastEditedById")
|
|
||||||
.HasPrincipalKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.SetNull)
|
|
||||||
.HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
|
|
||||||
|
|
||||||
b.HasOne("Content.Server.Database.Round", "Round")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("RoundId")
|
|
||||||
.HasConstraintName("FK_server_role_ban_round_round_id");
|
|
||||||
|
|
||||||
b.OwnsOne("Content.Server.Database.TypedHwid", "HWId", b1 =>
|
|
||||||
{
|
|
||||||
b1.Property<int>("ServerRoleBanId")
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasColumnName("server_role_ban_id");
|
|
||||||
|
|
||||||
b1.Property<byte[]>("Hwid")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("BLOB")
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
b1.Property<int>("Type")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("hwid_type");
|
|
||||||
|
|
||||||
b1.HasKey("ServerRoleBanId");
|
|
||||||
|
|
||||||
b1.ToTable("server_role_ban");
|
|
||||||
|
|
||||||
b1.WithOwner()
|
|
||||||
.HasForeignKey("ServerRoleBanId")
|
|
||||||
.HasConstraintName("FK_server_role_ban_server_role_ban_server_role_ban_id");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.Navigation("CreatedBy");
|
|
||||||
|
|
||||||
b.Navigation("HWId");
|
|
||||||
|
|
||||||
b.Navigation("LastEditedBy");
|
|
||||||
|
|
||||||
b.Navigation("Round");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
|
|
||||||
.WithOne("Unban")
|
|
||||||
.HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("FK_server_role_unban_server_role_ban_ban_id");
|
|
||||||
|
|
||||||
b.Navigation("Ban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Content.Server.Database.ServerBan", "Ban")
|
|
||||||
.WithOne("Unban")
|
|
||||||
.HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("FK_server_unban_server_ban_ban_id");
|
|
||||||
|
|
||||||
b.Navigation("Ban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||||
@@ -1912,6 +1899,18 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Profile");
|
b.Navigation("Profile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Unban", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Ban", "Ban")
|
||||||
|
.WithOne("Unban")
|
||||||
|
.HasForeignKey("Content.Server.Database.Unban", "BanId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_unban_ban_ban_id");
|
||||||
|
|
||||||
|
b.Navigation("Ban");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlayerRound", b =>
|
modelBuilder.Entity("PlayerRound", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Player", null)
|
b.HasOne("Content.Server.Database.Player", null)
|
||||||
@@ -1946,6 +1945,23 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Flags");
|
b.Navigation("Flags");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Ban", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Addresses");
|
||||||
|
|
||||||
|
b.Navigation("BanHits");
|
||||||
|
|
||||||
|
b.Navigation("Hwids");
|
||||||
|
|
||||||
|
b.Navigation("Players");
|
||||||
|
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("Rounds");
|
||||||
|
|
||||||
|
b.Navigation("Unban");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("BanHits");
|
b.Navigation("BanHits");
|
||||||
@@ -1975,10 +1991,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
|
|
||||||
b.Navigation("AdminServerBansLastEdited");
|
b.Navigation("AdminServerBansLastEdited");
|
||||||
|
|
||||||
b.Navigation("AdminServerRoleBansCreated");
|
|
||||||
|
|
||||||
b.Navigation("AdminServerRoleBansLastEdited");
|
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsCreated");
|
b.Navigation("AdminWatchlistsCreated");
|
||||||
|
|
||||||
b.Navigation("AdminWatchlistsDeleted");
|
b.Navigation("AdminWatchlistsDeleted");
|
||||||
@@ -2027,18 +2039,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
|
|
||||||
b.Navigation("Rounds");
|
b.Navigation("Rounds");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("BanHits");
|
|
||||||
|
|
||||||
b.Navigation("Unban");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Unban");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
328
Content.Server.Database/Model.Ban.cs
Normal file
328
Content.Server.Database/Model.Ban.cs
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NpgsqlTypes;
|
||||||
|
|
||||||
|
// ReSharper disable EntityFramework.ModelValidation.UnlimitedStringLength
|
||||||
|
|
||||||
|
namespace Content.Server.Database;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Contains model definitions primarily related to bans.
|
||||||
|
//
|
||||||
|
|
||||||
|
internal static class ModelBan
|
||||||
|
{
|
||||||
|
public static void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<Ban>()
|
||||||
|
.HasOne(b => b.CreatedBy)
|
||||||
|
.WithMany(pl => pl.AdminServerBansCreated)
|
||||||
|
.HasForeignKey(b => b.BanningAdmin)
|
||||||
|
.HasPrincipalKey(pl => pl.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Ban>()
|
||||||
|
.HasOne(b => b.LastEditedBy)
|
||||||
|
.WithMany(pl => pl.AdminServerBansLastEdited)
|
||||||
|
.HasForeignKey(b => b.LastEditedById)
|
||||||
|
.HasPrincipalKey(pl => pl.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
modelBuilder.Entity<BanPlayer>()
|
||||||
|
.HasIndex(bp => new { bp.UserId, bp.BanId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<BanHwid>()
|
||||||
|
.OwnsOne(bp => bp.HWId)
|
||||||
|
.Property(hwid => hwid.Hwid)
|
||||||
|
.HasColumnName("hwid");
|
||||||
|
|
||||||
|
modelBuilder.Entity<BanRole>()
|
||||||
|
.HasIndex(bp => new { bp.RoleType, bp.RoleId, bp.BanId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<BanRound>()
|
||||||
|
.HasIndex(bp => new { bp.RoundId, bp.BanId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
// Following indices have to be made manually by migration, due to limitations in EF Core:
|
||||||
|
// https://github.com/dotnet/efcore/issues/11336
|
||||||
|
// https://github.com/npgsql/efcore.pg/issues/2567
|
||||||
|
// modelBuilder.Entity<BanAddress>()
|
||||||
|
// .HasIndex(bp => new { bp.Address, bp.BanId })
|
||||||
|
// .IsUnique();
|
||||||
|
// modelBuilder.Entity<BanHwid>()
|
||||||
|
// .HasIndex(hwid => new { hwid.HWId.Type, hwid.HWId.Hwid, hwid.Hwid })
|
||||||
|
// .IsUnique();
|
||||||
|
// (postgres only)
|
||||||
|
// modelBuilder.Entity<BanAddress>()
|
||||||
|
// .HasIndex(ba => ba.Address)
|
||||||
|
// .IncludeProperties(ba => ba.BanId)
|
||||||
|
// .IsUnique()
|
||||||
|
// .HasMethod("gist")
|
||||||
|
// .HasOperators("inet_ops");
|
||||||
|
|
||||||
|
modelBuilder.Entity<Ban>()
|
||||||
|
.ToTable(t => t.HasCheckConstraint("NoExemptOnRoleBan", $"type = {(int)BanType.Server} OR exempt_flags = 0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a ban of some kind.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Bans come in two types: <see cref="BanType.Server"/> and <see cref="BanType.Role"/>,
|
||||||
|
/// distinguished with <see cref="Type"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Bans have one or more "matching data", these being <see cref="BanAddress"/>, <see cref="BanPlayer"/>,
|
||||||
|
/// and <see cref="BanHwid"/> entities. If a player's connection info matches any of these,
|
||||||
|
/// the ban's effects will apply to that player.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Bans can be set to expire after a certain point in time, or be permanent. They can also be removed manually
|
||||||
|
/// ("unbanned") by an admin, which is stored as an <see cref="Unban"/> entity existing for this ban.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class Ban
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this is a role or server ban.
|
||||||
|
/// </summary>
|
||||||
|
public required BanType Type { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan PlaytimeAtNote { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time when the ban was applied by an administrator.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime BanTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time the ban will expire. If null, the ban is permanent and will not expire naturally.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ExpirationTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The administrator-stated reason for applying the ban.
|
||||||
|
/// </summary>
|
||||||
|
public string Reason { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The severity of the incident
|
||||||
|
/// </summary>
|
||||||
|
public NoteSeverity Severity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User ID of the admin that initially applied the ban.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(CreatedBy))]
|
||||||
|
public Guid? BanningAdmin { get; set; }
|
||||||
|
|
||||||
|
public Player? CreatedBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User ID of the admin that last edited the note
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(LastEditedBy))]
|
||||||
|
public Guid? LastEditedById { get; set; }
|
||||||
|
|
||||||
|
public Player? LastEditedBy { get; set; }
|
||||||
|
public DateTime? LastEditedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ServerBanExemptFlags ExemptFlags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this ban should be automatically deleted from the database when it expires.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This isn't done automatically by the game,
|
||||||
|
/// you will need to set up something like a cron job to clear this from your database,
|
||||||
|
/// using a command like this:
|
||||||
|
/// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()"
|
||||||
|
/// </remarks>
|
||||||
|
public bool AutoDelete { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to display this ban in the admin remarks (notes) panel
|
||||||
|
/// </summary>
|
||||||
|
public bool Hidden { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If present, an administrator has manually repealed this ban.
|
||||||
|
/// </summary>
|
||||||
|
public Unban? Unban { get; set; }
|
||||||
|
|
||||||
|
public List<BanRound>? Rounds { get; set; }
|
||||||
|
public List<BanPlayer>? Players { get; set; }
|
||||||
|
public List<BanAddress>? Addresses { get; set; }
|
||||||
|
public List<BanHwid>? Hwids { get; set; }
|
||||||
|
public List<BanRole>? Roles { get; set; }
|
||||||
|
public List<ServerBanHit>? BanHits { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base type for entities that specify ban matching data.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBanSelector
|
||||||
|
{
|
||||||
|
int BanId { get; }
|
||||||
|
Ban? Ban { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a ban was related to a round (e.g. placed on that round).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BanRound
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the ban to which this round was relevant.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the round to which this ban was relevant to.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Round))]
|
||||||
|
public int RoundId { get; set; }
|
||||||
|
|
||||||
|
public Round? Round { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a player that a <see cref="T:Database.Ban"/> matches.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BanPlayer : IBanSelector
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The user ID of the banned player.
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the ban to which this applies.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies an IP address range that a <see cref="T:Database.Ban"/> matches.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BanAddress : IBanSelector
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The address range being matched.
|
||||||
|
/// </summary>
|
||||||
|
public required NpgsqlInet Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the ban to which this applies.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a HWID that a <see cref="T:Database.Ban"/> matches.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BanHwid : IBanSelector
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The HWID being matched.
|
||||||
|
/// </summary>
|
||||||
|
public required TypedHwid HWId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the ban to which this applies.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single role banned among a greater role ban record.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="Ban"/>s of type <see cref="BanType.Role"/> should have one or more <see cref="BanRole"/>s
|
||||||
|
/// to store which roles are actually banned.
|
||||||
|
/// It is invalid for <see cref="BanType.Server"/> bans to have <see cref="BanRole"/> entities.
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class BanRole
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What type of role is being banned. For example <c>Job</c> or <c>Antag</c>.
|
||||||
|
/// </summary>
|
||||||
|
public required string RoleType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the role being banned. This is probably something like a prototype.
|
||||||
|
/// </summary>
|
||||||
|
public required string RoleId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the ban to which this applies.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An explicit repeal of a <see cref="Ban"/> by an administrator.
|
||||||
|
/// Having an entry for a ban neutralizes it.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Unban
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of ban that is being repealed.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignKey(nameof(Ban))]
|
||||||
|
public int BanId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ban that is being repealed.
|
||||||
|
/// </summary>
|
||||||
|
public Ban? Ban { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The admin that repealed the ban.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? UnbanningAdmin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time the ban was repealed.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime UnbanTime { get; set; }
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ using System.Net;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NpgsqlTypes;
|
|
||||||
|
|
||||||
namespace Content.Server.Database
|
namespace Content.Server.Database
|
||||||
{
|
{
|
||||||
@@ -31,13 +30,17 @@ namespace Content.Server.Database
|
|||||||
public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
|
public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
|
||||||
public DbSet<Whitelist> Whitelist { get; set; } = null!;
|
public DbSet<Whitelist> Whitelist { get; set; } = null!;
|
||||||
public DbSet<Blacklist> Blacklist { get; set; } = null!;
|
public DbSet<Blacklist> Blacklist { get; set; } = null!;
|
||||||
public DbSet<ServerBan> Ban { get; set; } = default!;
|
public DbSet<Ban> Ban { get; set; } = default!;
|
||||||
public DbSet<ServerUnban> Unban { get; set; } = default!;
|
public DbSet<BanRound> BanRound { get; set; } = default!;
|
||||||
|
public DbSet<BanPlayer> BanPlayer { get; set; } = default!;
|
||||||
|
public DbSet<BanAddress> BanAddress { get; set; } = default!;
|
||||||
|
public DbSet<BanHwid> BanHwid { get; set; } = default!;
|
||||||
|
public DbSet<BanRole> BanRole { get; set; } = default!;
|
||||||
|
public DbSet<Unban> Unban { get; set; } = default!;
|
||||||
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
|
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
|
||||||
public DbSet<ConnectionLog> ConnectionLog { get; set; } = default!;
|
public DbSet<ConnectionLog> ConnectionLog { get; set; } = default!;
|
||||||
public DbSet<ServerBanHit> ServerBanHit { get; set; } = default!;
|
public DbSet<ServerBanHit> ServerBanHit { get; set; } = default!;
|
||||||
public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
|
|
||||||
public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
|
|
||||||
public DbSet<PlayTime> PlayTime { get; set; } = default!;
|
public DbSet<PlayTime> PlayTime { get; set; } = default!;
|
||||||
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
|
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
|
||||||
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
||||||
@@ -145,43 +148,11 @@ namespace Content.Server.Database
|
|||||||
modelBuilder.Entity<AdminLogPlayer>()
|
modelBuilder.Entity<AdminLogPlayer>()
|
||||||
.HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
|
.HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.HasIndex(p => p.PlayerUserId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.HasIndex(p => p.Address);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.HasIndex(p => p.PlayerUserId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerUnban>()
|
|
||||||
.HasIndex(p => p.BanId)
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>().ToTable(t =>
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
|
|
||||||
|
|
||||||
// Ban exemption can't have flags 0 since that wouldn't exempt anything.
|
// Ban exemption can't have flags 0 since that wouldn't exempt anything.
|
||||||
// The row should be removed if setting to 0.
|
// The row should be removed if setting to 0.
|
||||||
modelBuilder.Entity<ServerBanExemption>().ToTable(t =>
|
modelBuilder.Entity<ServerBanExemption>().ToTable(t =>
|
||||||
t.HasCheckConstraint("FlagsNotZero", "flags != 0"));
|
t.HasCheckConstraint("FlagsNotZero", "flags != 0"));
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.HasIndex(p => p.PlayerUserId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.HasIndex(p => p.Address);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.HasIndex(p => p.PlayerUserId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleUnban>()
|
|
||||||
.HasIndex(p => p.BanId)
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>().ToTable(t =>
|
|
||||||
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
|
|
||||||
|
|
||||||
modelBuilder.Entity<Player>()
|
modelBuilder.Entity<Player>()
|
||||||
.HasIndex(p => p.UserId)
|
.HasIndex(p => p.UserId)
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
@@ -296,34 +267,6 @@ namespace Content.Server.Database
|
|||||||
t.HasCheckConstraint("NotDismissedAndSeen",
|
t.HasCheckConstraint("NotDismissedAndSeen",
|
||||||
"NOT dismissed OR seen"));
|
"NOT dismissed OR seen"));
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.HasOne(ban => ban.CreatedBy)
|
|
||||||
.WithMany(author => author.AdminServerBansCreated)
|
|
||||||
.HasForeignKey(ban => ban.BanningAdmin)
|
|
||||||
.HasPrincipalKey(author => author.UserId)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.HasOne(ban => ban.LastEditedBy)
|
|
||||||
.WithMany(author => author.AdminServerBansLastEdited)
|
|
||||||
.HasForeignKey(ban => ban.LastEditedById)
|
|
||||||
.HasPrincipalKey(author => author.UserId)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.HasOne(ban => ban.CreatedBy)
|
|
||||||
.WithMany(author => author.AdminServerRoleBansCreated)
|
|
||||||
.HasForeignKey(ban => ban.BanningAdmin)
|
|
||||||
.HasPrincipalKey(author => author.UserId)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.HasOne(ban => ban.LastEditedBy)
|
|
||||||
.WithMany(author => author.AdminServerRoleBansLastEdited)
|
|
||||||
.HasForeignKey(ban => ban.LastEditedById)
|
|
||||||
.HasPrincipalKey(author => author.UserId)
|
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
|
||||||
|
|
||||||
modelBuilder.Entity<RoleWhitelist>()
|
modelBuilder.Entity<RoleWhitelist>()
|
||||||
.HasOne(w => w.Player)
|
.HasOne(w => w.Player)
|
||||||
.WithMany(p => p.JobWhitelists)
|
.WithMany(p => p.JobWhitelists)
|
||||||
@@ -342,26 +285,6 @@ namespace Content.Server.Database
|
|||||||
.Property(p => p.Type)
|
.Property(p => p.Type)
|
||||||
.HasDefaultValue(HwidType.Legacy);
|
.HasDefaultValue(HwidType.Legacy);
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.OwnsOne(p => p.HWId)
|
|
||||||
.Property(p => p.Hwid)
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerBan>()
|
|
||||||
.OwnsOne(p => p.HWId)
|
|
||||||
.Property(p => p.Type)
|
|
||||||
.HasDefaultValue(HwidType.Legacy);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.OwnsOne(p => p.HWId)
|
|
||||||
.Property(p => p.Hwid)
|
|
||||||
.HasColumnName("hwid");
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>()
|
|
||||||
.OwnsOne(p => p.HWId)
|
|
||||||
.Property(p => p.Type)
|
|
||||||
.HasDefaultValue(HwidType.Legacy);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ConnectionLog>()
|
modelBuilder.Entity<ConnectionLog>()
|
||||||
.OwnsOne(p => p.HWId)
|
.OwnsOne(p => p.HWId)
|
||||||
.Property(p => p.Hwid)
|
.Property(p => p.Hwid)
|
||||||
@@ -371,6 +294,8 @@ namespace Content.Server.Database
|
|||||||
.OwnsOne(p => p.HWId)
|
.OwnsOne(p => p.HWId)
|
||||||
.Property(p => p.Type)
|
.Property(p => p.Type)
|
||||||
.HasDefaultValue(HwidType.Legacy);
|
.HasDefaultValue(HwidType.Legacy);
|
||||||
|
|
||||||
|
ModelBan.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||||
@@ -591,10 +516,8 @@ namespace Content.Server.Database
|
|||||||
public List<AdminMessage> AdminMessagesCreated { get; set; } = null!;
|
public List<AdminMessage> AdminMessagesCreated { get; set; } = null!;
|
||||||
public List<AdminMessage> AdminMessagesLastEdited { get; set; } = null!;
|
public List<AdminMessage> AdminMessagesLastEdited { get; set; } = null!;
|
||||||
public List<AdminMessage> AdminMessagesDeleted { get; set; } = null!;
|
public List<AdminMessage> AdminMessagesDeleted { get; set; } = null!;
|
||||||
public List<ServerBan> AdminServerBansCreated { get; set; } = null!;
|
public List<Ban> AdminServerBansCreated { get; set; } = null!;
|
||||||
public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
|
public List<Ban> AdminServerBansLastEdited { get; set; } = null!;
|
||||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
|
||||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
|
||||||
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,30 +647,6 @@ namespace Content.Server.Database
|
|||||||
[ForeignKey("RoundId,LogId")] public AdminLog Log { get; set; } = default!;
|
[ForeignKey("RoundId,LogId")] public AdminLog Log { get; set; } = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by SS14.Admin
|
|
||||||
public interface IBanCommon<TUnban> where TUnban : IUnbanCommon
|
|
||||||
{
|
|
||||||
int Id { get; set; }
|
|
||||||
Guid? PlayerUserId { get; set; }
|
|
||||||
NpgsqlInet? Address { get; set; }
|
|
||||||
TypedHwid? HWId { get; set; }
|
|
||||||
DateTime BanTime { get; set; }
|
|
||||||
DateTime? ExpirationTime { get; set; }
|
|
||||||
string Reason { get; set; }
|
|
||||||
NoteSeverity Severity { get; set; }
|
|
||||||
Guid? BanningAdmin { get; set; }
|
|
||||||
TUnban? Unban { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by SS14.Admin
|
|
||||||
public interface IUnbanCommon
|
|
||||||
{
|
|
||||||
int Id { get; set; }
|
|
||||||
int BanId { get; set; }
|
|
||||||
Guid? UnbanningAdmin { get; set; }
|
|
||||||
DateTime UnbanTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flags for use with <see cref="ServerBanExemption"/>.
|
/// Flags for use with <see cref="ServerBanExemption"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -785,138 +684,6 @@ namespace Content.Server.Database
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A ban from playing on the server.
|
|
||||||
/// If an incoming connection matches any of UserID, IP, or HWID, they will be blocked from joining the server.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// At least one of UserID, IP, or HWID must be given (otherwise the ban would match nothing).
|
|
||||||
/// </remarks>
|
|
||||||
[Table("server_ban"), Index(nameof(PlayerUserId))]
|
|
||||||
public class ServerBan : IBanCommon<ServerUnban>
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
[ForeignKey("Round")]
|
|
||||||
public int? RoundId { get; set; }
|
|
||||||
public Round? Round { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The user ID of the banned player.
|
|
||||||
/// </summary>
|
|
||||||
public Guid? PlayerUserId { get; set; }
|
|
||||||
[Required] public TimeSpan PlaytimeAtNote { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CIDR IP address range of the ban. The whole range can match the ban.
|
|
||||||
/// </summary>
|
|
||||||
public NpgsqlInet? Address { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hardware ID of the banned player.
|
|
||||||
/// </summary>
|
|
||||||
public TypedHwid? HWId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time when the ban was applied by an administrator.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime BanTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time the ban will expire. If null, the ban is permanent and will not expire naturally.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? ExpirationTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The administrator-stated reason for applying the ban.
|
|
||||||
/// </summary>
|
|
||||||
public string Reason { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The severity of the incident
|
|
||||||
/// </summary>
|
|
||||||
public NoteSeverity Severity { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User ID of the admin that applied the ban.
|
|
||||||
/// </summary>
|
|
||||||
[ForeignKey("CreatedBy")]
|
|
||||||
public Guid? BanningAdmin { get; set; }
|
|
||||||
|
|
||||||
public Player? CreatedBy { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User ID of the admin that last edited the note
|
|
||||||
/// </summary>
|
|
||||||
[ForeignKey("LastEditedBy")]
|
|
||||||
public Guid? LastEditedById { get; set; }
|
|
||||||
|
|
||||||
public Player? LastEditedBy { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When the ban was last edited
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? LastEditedAt { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ServerBanExemptFlags ExemptFlags { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If present, an administrator has manually repealed this ban.
|
|
||||||
/// </summary>
|
|
||||||
public ServerUnban? Unban { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this ban should be automatically deleted from the database when it expires.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This isn't done automatically by the game,
|
|
||||||
/// you will need to set up something like a cron job to clear this from your database,
|
|
||||||
/// using a command like this:
|
|
||||||
/// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time < NOW()"
|
|
||||||
/// </remarks>
|
|
||||||
public bool AutoDelete { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to display this ban in the admin remarks (notes) panel
|
|
||||||
/// </summary>
|
|
||||||
public bool Hidden { get; set; }
|
|
||||||
|
|
||||||
public List<ServerBanHit> BanHits { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An explicit repeal of a <see cref="ServerBan"/> by an administrator.
|
|
||||||
/// Having an entry for a ban neutralizes it.
|
|
||||||
/// </summary>
|
|
||||||
[Table("server_unban")]
|
|
||||||
public class ServerUnban : IUnbanCommon
|
|
||||||
{
|
|
||||||
[Column("unban_id")] public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of ban that is being repealed.
|
|
||||||
/// </summary>
|
|
||||||
public int BanId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ban that is being repealed.
|
|
||||||
/// </summary>
|
|
||||||
public ServerBan Ban { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The admin that repealed the ban.
|
|
||||||
/// </summary>
|
|
||||||
public Guid? UnbanningAdmin { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time the ban repealed.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime UnbanTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An exemption for a specific user to a certain type of <see cref="ServerBan"/>.
|
/// An exemption for a specific user to a certain type of <see cref="ServerBan"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -937,7 +704,7 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ban flags to exempt this player from.
|
/// The ban flags to exempt this player from.
|
||||||
/// If any bit overlaps <see cref="ServerBan.ExemptFlags"/>, the ban is ignored.
|
/// If any bit overlaps <see cref="Ban.ExemptFlags"/>, the ban is ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServerBanExemptFlags Flags { get; set; }
|
public ServerBanExemptFlags Flags { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1000,54 +767,10 @@ namespace Content.Server.Database
|
|||||||
public int BanId { get; set; }
|
public int BanId { get; set; }
|
||||||
public int ConnectionId { get; set; }
|
public int ConnectionId { get; set; }
|
||||||
|
|
||||||
public ServerBan Ban { get; set; } = null!;
|
public Ban Ban { get; set; } = null!;
|
||||||
public ConnectionLog Connection { get; set; } = null!;
|
public ConnectionLog Connection { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Table("server_role_ban"), Index(nameof(PlayerUserId))]
|
|
||||||
public sealed class ServerRoleBan : IBanCommon<ServerRoleUnban>
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int? RoundId { get; set; }
|
|
||||||
public Round? Round { get; set; }
|
|
||||||
public Guid? PlayerUserId { get; set; }
|
|
||||||
[Required] public TimeSpan PlaytimeAtNote { get; set; }
|
|
||||||
public NpgsqlInet? Address { get; set; }
|
|
||||||
public TypedHwid? HWId { get; set; }
|
|
||||||
|
|
||||||
public DateTime BanTime { get; set; }
|
|
||||||
|
|
||||||
public DateTime? ExpirationTime { get; set; }
|
|
||||||
|
|
||||||
public string Reason { get; set; } = null!;
|
|
||||||
|
|
||||||
public NoteSeverity Severity { get; set; }
|
|
||||||
[ForeignKey("CreatedBy")] public Guid? BanningAdmin { get; set; }
|
|
||||||
public Player? CreatedBy { get; set; }
|
|
||||||
|
|
||||||
[ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
|
|
||||||
public Player? LastEditedBy { get; set; }
|
|
||||||
public DateTime? LastEditedAt { get; set; }
|
|
||||||
|
|
||||||
public ServerRoleUnban? Unban { get; set; }
|
|
||||||
public bool Hidden { get; set; }
|
|
||||||
|
|
||||||
public string RoleId { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Table("server_role_unban")]
|
|
||||||
public sealed class ServerRoleUnban : IUnbanCommon
|
|
||||||
{
|
|
||||||
[Column("role_unban_id")] public int Id { get; set; }
|
|
||||||
|
|
||||||
public int BanId { get; set; }
|
|
||||||
public ServerRoleBan Ban { get; set; } = null!;
|
|
||||||
|
|
||||||
public Guid? UnbanningAdmin { get; set; }
|
|
||||||
|
|
||||||
public DateTime UnbanTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Table("play_time")]
|
[Table("play_time")]
|
||||||
public sealed class PlayTime
|
public sealed class PlayTime
|
||||||
{
|
{
|
||||||
@@ -1247,31 +970,31 @@ namespace Content.Server.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The reason for the ban.
|
/// The reason for the ban.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="ServerBan.Reason"/>
|
/// <seealso cref="Ban.Reason"/>
|
||||||
public string Reason { get; set; } = "";
|
public string Reason { get; set; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exemptions granted to the ban.
|
/// Exemptions granted to the ban.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="ServerBan.ExemptFlags"/>
|
/// <seealso cref="Ban.ExemptFlags"/>
|
||||||
public ServerBanExemptFlags ExemptFlags { get; set; }
|
public ServerBanExemptFlags ExemptFlags { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Severity of the ban
|
/// Severity of the ban
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="ServerBan.Severity"/>
|
/// <seealso cref="Ban.Severity"/>
|
||||||
public NoteSeverity Severity { get; set; }
|
public NoteSeverity Severity { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ban will be automatically deleted once expired.
|
/// Ban will be automatically deleted once expired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="ServerBan.AutoDelete"/>
|
/// <seealso cref="Ban.AutoDelete"/>
|
||||||
public bool AutoDelete { get; set; }
|
public bool AutoDelete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ban is not visible to players in the remarks menu.
|
/// Ban is not visible to players in the remarks menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="ServerBan.Hidden"/>
|
/// <seealso cref="Ban.Hidden"/>
|
||||||
public bool Hidden { get; set; }
|
public bool Hidden { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,7 @@ namespace Content.Server.Database
|
|||||||
// ReSharper disable StringLiteralTypo
|
// ReSharper disable StringLiteralTypo
|
||||||
// Enforce that an address cannot be IPv6-mapped IPv4.
|
// Enforce that an address cannot be IPv6-mapped IPv4.
|
||||||
// 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<ServerBan>().ToTable(t =>
|
modelBuilder.Entity<BanAddress>().ToTable(t =>
|
||||||
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"));
|
|
||||||
|
|
||||||
modelBuilder.Entity<ServerRoleBan>().ToTable( t =>
|
|
||||||
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"));
|
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"));
|
||||||
|
|
||||||
modelBuilder.Entity<Player>().ToTable(t =>
|
modelBuilder.Entity<Player>().ToTable(t =>
|
||||||
|
|||||||
@@ -58,13 +58,7 @@ namespace Content.Server.Database
|
|||||||
);
|
);
|
||||||
|
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.Entity<ServerBan>()
|
.Entity<BanAddress>()
|
||||||
.Property(e => e.Address)
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasConversion(ipMaskConverter);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ServerRoleBan>()
|
|
||||||
.Property(e => e.Address)
|
.Property(e => e.Address)
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasConversion(ipMaskConverter);
|
.HasConversion(ipMaskConverter);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Administration.BanList;
|
using Content.Shared.Administration.BanList;
|
||||||
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
@@ -22,8 +25,8 @@ public sealed class BanListEui : BaseEui
|
|||||||
|
|
||||||
private Guid BanListPlayer { get; set; }
|
private Guid BanListPlayer { get; set; }
|
||||||
private string BanListPlayerName { get; set; } = string.Empty;
|
private string BanListPlayerName { get; set; } = string.Empty;
|
||||||
private List<SharedServerBan> Bans { get; } = new();
|
private List<SharedBan> Bans { get; } = new();
|
||||||
private List<SharedServerRoleBan> RoleBans { get; } = new();
|
private List<SharedBan> RoleBans { get; } = new();
|
||||||
|
|
||||||
public override void Opened()
|
public override void Opened()
|
||||||
{
|
{
|
||||||
@@ -54,74 +57,38 @@ public sealed class BanListEui : BaseEui
|
|||||||
|
|
||||||
private async Task LoadBans(NetUserId userId)
|
private async Task LoadBans(NetUserId userId)
|
||||||
{
|
{
|
||||||
foreach (var ban in await _db.GetServerBansAsync(null, userId, null, null))
|
await LoadBansCore(userId, BanType.Server, Bans);
|
||||||
{
|
await LoadBansCore(userId, BanType.Role, RoleBans);
|
||||||
SharedServerUnban? unban = null;
|
|
||||||
if (ban.Unban is { } unbanDef)
|
|
||||||
{
|
|
||||||
var unbanningAdmin = unbanDef.UnbanningAdmin == null
|
|
||||||
? null
|
|
||||||
: (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username;
|
|
||||||
unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
(string, int cidrMask)? ip = ("*Hidden*", 0);
|
|
||||||
var hwid = "*Hidden*";
|
|
||||||
|
|
||||||
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
|
|
||||||
{
|
|
||||||
ip = ban.Address is { } address
|
|
||||||
? (address.address.ToString(), address.cidrMask)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
hwid = ban.HWId?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Bans.Add(new SharedServerBan(
|
|
||||||
ban.Id,
|
|
||||||
ban.UserId,
|
|
||||||
ip,
|
|
||||||
hwid,
|
|
||||||
ban.BanTime.UtcDateTime,
|
|
||||||
ban.ExpirationTime?.UtcDateTime,
|
|
||||||
ban.Reason,
|
|
||||||
ban.BanningAdmin == null
|
|
||||||
? null
|
|
||||||
: (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username,
|
|
||||||
unban
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadRoleBans(NetUserId userId)
|
private async Task LoadBansCore(NetUserId userId, BanType banType, List<SharedBan> list)
|
||||||
{
|
{
|
||||||
foreach (var ban in await _db.GetServerRoleBansAsync(null, userId, null, null))
|
foreach (var ban in await _db.GetBansAsync(null, userId, null, null, type: banType))
|
||||||
{
|
{
|
||||||
SharedServerUnban? unban = null;
|
SharedUnban? unban = null;
|
||||||
if (ban.Unban is { } unbanDef)
|
if (ban.Unban is { } unbanDef)
|
||||||
{
|
{
|
||||||
var unbanningAdmin = unbanDef.UnbanningAdmin == null
|
var unbanningAdmin = unbanDef.UnbanningAdmin == null
|
||||||
? null
|
? null
|
||||||
: (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username;
|
: (await _playerLocator.LookupIdAsync(unbanDef.UnbanningAdmin.Value))?.Username;
|
||||||
unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
|
unban = new SharedUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
(string, int cidrMask)? ip = ("*Hidden*", 0);
|
ImmutableArray<(string, int cidrMask)> ips = [("*Hidden*", 0)];
|
||||||
var hwid = "*Hidden*";
|
ImmutableArray<string> hwids = ["*Hidden*"];
|
||||||
|
|
||||||
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
|
if (_admins.HasAdminFlag(Player, AdminFlags.Pii))
|
||||||
{
|
{
|
||||||
ip = ban.Address is { } address
|
ips = [..ban.Addresses.Select(a => (a.address.ToString(), a.cidrMask))];
|
||||||
? (address.address.ToString(), address.cidrMask)
|
hwids = [..ban.HWIds.Select(h => h.ToString())];
|
||||||
: null;
|
|
||||||
|
|
||||||
hwid = ban.HWId?.ToString();
|
|
||||||
}
|
}
|
||||||
RoleBans.Add(new SharedServerRoleBan(
|
|
||||||
|
list.Add(new SharedBan(
|
||||||
ban.Id,
|
ban.Id,
|
||||||
ban.UserId,
|
ban.Type,
|
||||||
ip,
|
ban.UserIds,
|
||||||
hwid,
|
ips,
|
||||||
|
hwids,
|
||||||
ban.BanTime.UtcDateTime,
|
ban.BanTime.UtcDateTime,
|
||||||
ban.ExpirationTime?.UtcDateTime,
|
ban.ExpirationTime?.UtcDateTime,
|
||||||
ban.Reason,
|
ban.Reason,
|
||||||
@@ -129,7 +96,7 @@ public sealed class BanListEui : BaseEui
|
|||||||
? null
|
? null
|
||||||
: (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username,
|
: (await _playerLocator.LookupIdAsync(ban.BanningAdmin.Value))?.Username,
|
||||||
unban,
|
unban,
|
||||||
ban.Role
|
ban.Roles
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +111,6 @@ public sealed class BanListEui : BaseEui
|
|||||||
string.Empty;
|
string.Empty;
|
||||||
|
|
||||||
await LoadBans(userId);
|
await LoadBans(userId);
|
||||||
await LoadRoleBans(userId);
|
|
||||||
|
|
||||||
StateDirty();
|
StateDirty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ public sealed class BanPanelEui : BaseEui
|
|||||||
private string PlayerName { get; set; } = string.Empty;
|
private string PlayerName { get; set; } = string.Empty;
|
||||||
private IPAddress? LastAddress { get; set; }
|
private IPAddress? LastAddress { get; set; }
|
||||||
private ImmutableTypedHwid? LastHwid { get; set; }
|
private ImmutableTypedHwid? LastHwid { get; set; }
|
||||||
private const int Ipv4_CIDR = 32;
|
private const int Ipv4_CIDR = CreateBanInfo.DefaultMaskIpv4;
|
||||||
private const int Ipv6_CIDR = 64;
|
private const int Ipv6_CIDR = CreateBanInfo.DefaultMaskIpv6;
|
||||||
|
|
||||||
public BanPanelEui()
|
public BanPanelEui()
|
||||||
{
|
{
|
||||||
@@ -73,6 +73,15 @@ public sealed class BanPanelEui : BaseEui
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isRoleBan = ban.BannedJobs?.Length > 0 || ban.BannedAntags?.Length > 0;
|
||||||
|
|
||||||
|
CreateBanInfo banInfo = isRoleBan ? new CreateRoleBanInfo(ban.Reason) : new CreateServerBanInfo(ban.Reason);
|
||||||
|
|
||||||
|
banInfo.WithBanningAdmin(Player.UserId);
|
||||||
|
banInfo.WithSeverity(ban.Severity);
|
||||||
|
if (ban.BanDurationMinutes > 0)
|
||||||
|
banInfo.WithMinutes(ban.BanDurationMinutes);
|
||||||
|
|
||||||
(IPAddress, int)? addressRange = null;
|
(IPAddress, int)? addressRange = null;
|
||||||
if (ban.IpAddress is not null)
|
if (ban.IpAddress is not null)
|
||||||
{
|
{
|
||||||
@@ -113,69 +122,46 @@ public sealed class BanPanelEui : BaseEui
|
|||||||
targetHWid = ban.UseLastHwid ? located.LastHWId : ban.Hwid;
|
targetHWid = ban.UseLastHwid ? located.LastHWId : ban.Hwid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ban.BannedJobs?.Length > 0 || ban.BannedAntags?.Length > 0)
|
if (addressRange != null)
|
||||||
|
banInfo.AddAddressRange(addressRange.Value);
|
||||||
|
|
||||||
|
if (targetUid != null)
|
||||||
|
banInfo.AddUser(targetUid.Value, ban.Target!);
|
||||||
|
|
||||||
|
banInfo.AddHWId(targetHWid);
|
||||||
|
|
||||||
|
if (isRoleBan)
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.UtcNow;
|
var roleBanInfo = (CreateRoleBanInfo)banInfo;
|
||||||
foreach (var role in ban.BannedJobs ?? [])
|
foreach (var row in ban.BannedJobs ?? [])
|
||||||
{
|
{
|
||||||
_banManager.CreateRoleBan(
|
roleBanInfo.AddJob(row);
|
||||||
targetUid,
|
|
||||||
ban.Target,
|
|
||||||
Player.UserId,
|
|
||||||
addressRange,
|
|
||||||
targetHWid,
|
|
||||||
role,
|
|
||||||
ban.BanDurationMinutes,
|
|
||||||
ban.Severity,
|
|
||||||
ban.Reason,
|
|
||||||
now
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var role in ban.BannedAntags ?? [])
|
foreach (var row in ban.BannedAntags ?? [])
|
||||||
{
|
{
|
||||||
_banManager.CreateRoleBan(
|
roleBanInfo.AddAntag(row);
|
||||||
targetUid,
|
|
||||||
ban.Target,
|
|
||||||
Player.UserId,
|
|
||||||
addressRange,
|
|
||||||
targetHWid,
|
|
||||||
role,
|
|
||||||
ban.BanDurationMinutes,
|
|
||||||
ban.Severity,
|
|
||||||
ban.Reason,
|
|
||||||
now
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Close();
|
_banManager.CreateRoleBan(roleBanInfo);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (ban.Erase && targetUid is not null)
|
|
||||||
{
|
{
|
||||||
try
|
if (ban.Erase && targetUid is not null)
|
||||||
{
|
{
|
||||||
if (_entities.TrySystem(out AdminSystem? adminSystem))
|
try
|
||||||
adminSystem.Erase(targetUid.Value);
|
{
|
||||||
|
if (_entities.TrySystem(out AdminSystem? adminSystem))
|
||||||
|
adminSystem.Erase(targetUid.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error while erasing banned player:\n{e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_sawmill.Error($"Error while erasing banned player:\n{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_banManager.CreateServerBan(
|
_banManager.CreateServerBan((CreateServerBanInfo)banInfo);
|
||||||
targetUid,
|
}
|
||||||
ban.Target,
|
|
||||||
Player.UserId,
|
|
||||||
addressRange,
|
|
||||||
targetHWid,
|
|
||||||
ban.BanDurationMinutes,
|
|
||||||
ban.Severity,
|
|
||||||
ban.Reason
|
|
||||||
);
|
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,15 @@ public sealed class BanCommand : LocalizedCommands
|
|||||||
var targetUid = located.UserId;
|
var targetUid = located.UserId;
|
||||||
var targetHWid = located.LastHWId;
|
var targetHWid = located.LastHWId;
|
||||||
|
|
||||||
_bans.CreateServerBan(targetUid, target, player?.UserId, null, targetHWid, minutes, severity, reason);
|
var banInfo = new CreateServerBanInfo(reason);
|
||||||
|
banInfo.WithBanningAdmin(player?.UserId);
|
||||||
|
banInfo.AddUser(targetUid, target);
|
||||||
|
banInfo.AddHWId(targetHWid);
|
||||||
|
if (minutes > 0)
|
||||||
|
banInfo.WithMinutes(minutes);
|
||||||
|
banInfo.WithSeverity(severity);
|
||||||
|
|
||||||
|
_bans.CreateServerBan(banInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public sealed class BanListCommand : LocalizedCommands
|
|||||||
|
|
||||||
if (shell.Player is not { } player)
|
if (shell.Player is not { } player)
|
||||||
{
|
{
|
||||||
var bans = await _dbManager.GetServerBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
|
var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, false);
|
||||||
|
|
||||||
if (bans.Count == 0)
|
if (bans.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -96,13 +96,20 @@ public sealed class DepartmentBanCommand : IConsoleCommand
|
|||||||
var targetUid = located.UserId;
|
var targetUid = located.UserId;
|
||||||
var targetHWid = located.LastHWId;
|
var targetHWid = located.LastHWId;
|
||||||
|
|
||||||
// If you are trying to remove the following variable, please don't. It's there because the note system groups role bans by time, reason and banning admin.
|
var banInfo = new CreateRoleBanInfo(reason);
|
||||||
// Without it the note list will get needlessly cluttered.
|
if (minutes > 0)
|
||||||
var now = DateTimeOffset.UtcNow;
|
banInfo.WithMinutes(minutes);
|
||||||
|
banInfo.AddUser(targetUid, located.Username);
|
||||||
|
banInfo.WithBanningAdmin(shell.Player?.UserId);
|
||||||
|
banInfo.AddHWId(targetHWid);
|
||||||
|
banInfo.WithSeverity(severity);
|
||||||
|
|
||||||
foreach (var job in departmentProto.Roles)
|
foreach (var job in departmentProto.Roles)
|
||||||
{
|
{
|
||||||
_banManager.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, now);
|
banInfo.AddJob(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_banManager.CreateRoleBan(banInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Content.Server.Administration.Notes;
|
|||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Commands;
|
namespace Content.Server.Administration.Commands;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ public sealed class OpenAdminNotesCommand : LocalizedCommands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _adminNotes.OpenEui(player, notedPlayer);
|
await _adminNotes.OpenEui(player, new NetUserId(notedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Content.Server.Administration.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ban = await _dbManager.GetServerBanAsync(banId);
|
var ban = await _dbManager.GetBanAsync(banId);
|
||||||
|
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
@@ -50,7 +50,7 @@ namespace Content.Server.Administration.Commands
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _dbManager.AddServerUnbanAsync(new ServerUnbanDef(banId, player?.UserId, DateTimeOffset.Now));
|
await _dbManager.AddUnbanAsync(new UnbanDef(banId, player?.UserId, DateTimeOffset.Now));
|
||||||
|
|
||||||
shell.WriteLine(Loc.GetString($"cmd-pardon-success", ("id", banId)));
|
shell.WriteLine(Loc.GetString($"cmd-pardon-success", ("id", banId)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Linq;
|
using Content.Server.Administration.Managers;
|
||||||
using System.Text;
|
|
||||||
using Content.Server.Administration.Managers;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -99,12 +97,29 @@ public sealed class RoleBanCommand : IConsoleCommand
|
|||||||
var targetUid = located.UserId;
|
var targetUid = located.UserId;
|
||||||
var targetHWid = located.LastHWId;
|
var targetHWid = located.LastHWId;
|
||||||
|
|
||||||
|
var banInfo = new CreateRoleBanInfo(reason);
|
||||||
|
if (minutes > 0)
|
||||||
|
banInfo.WithMinutes(minutes);
|
||||||
|
banInfo.AddUser(targetUid, located.Username);
|
||||||
|
banInfo.WithBanningAdmin(shell.Player?.UserId);
|
||||||
|
banInfo.AddHWId(targetHWid);
|
||||||
|
banInfo.WithSeverity(severity);
|
||||||
|
|
||||||
if (_proto.HasIndex<JobPrototype>(role))
|
if (_proto.HasIndex<JobPrototype>(role))
|
||||||
_bans.CreateRoleBan<JobPrototype>(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, role, minutes, severity, reason, DateTimeOffset.UtcNow);
|
{
|
||||||
|
banInfo.AddJob(new ProtoId<JobPrototype>(role));
|
||||||
|
}
|
||||||
else if (_proto.HasIndex<AntagPrototype>(role))
|
else if (_proto.HasIndex<AntagPrototype>(role))
|
||||||
_bans.CreateRoleBan<AntagPrototype>(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, role, minutes, severity, reason, DateTimeOffset.UtcNow);
|
{
|
||||||
|
banInfo.AddAntag(new ProtoId<AntagPrototype>(role));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", role)));
|
shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", role)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bans.CreateRoleBan(banInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System.Linq;
|
using Content.Server.Administration.BanList;
|
||||||
using System.Text;
|
|
||||||
using Content.Server.Administration.BanList;
|
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Server.Player;
|
using Content.Shared.Database;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Commands;
|
namespace Content.Server.Administration.Commands;
|
||||||
@@ -48,7 +46,7 @@ public sealed class RoleBanListCommand : IConsoleCommand
|
|||||||
if (shell.Player is not { } player)
|
if (shell.Player is not { } player)
|
||||||
{
|
{
|
||||||
|
|
||||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned);
|
var bans = await _dbManager.GetBansAsync(data.LastAddress, data.UserId, data.LastLegacyHWId, data.LastModernHWIds, includeUnbanned, type: BanType.Role);
|
||||||
|
|
||||||
if (bans.Count == 0)
|
if (bans.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -58,7 +56,7 @@ public sealed class RoleBanListCommand : IConsoleCommand
|
|||||||
|
|
||||||
foreach (var ban in bans)
|
foreach (var ban in bans)
|
||||||
{
|
{
|
||||||
var msg = $"ID: {ban.Id}: Role: {ban.Role} Reason: {ban.Reason}";
|
var msg = $"ID: {ban.Id}: Role(s): {string.Join(",", ban.Roles ?? [])} Reason: {ban.Reason}";
|
||||||
shell.WriteLine(msg);
|
shell.WriteLine(msg);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -41,14 +41,8 @@ public sealed partial class BanManager
|
|||||||
|
|
||||||
private async void ProcessBanNotification(BanNotificationData data)
|
private async void ProcessBanNotification(BanNotificationData data)
|
||||||
{
|
{
|
||||||
if ((await _entryManager.ServerEntity).Id == data.ServerId)
|
|
||||||
{
|
|
||||||
_sawmill.Verbose("Not processing ban notification: came from this server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
|
_sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
|
||||||
var ban = await _db.GetServerBanAsync(data.BanId);
|
var ban = await _db.GetBanAsync(data.BanId);
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
_sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
|
_sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
|
||||||
@@ -86,15 +80,5 @@ public sealed partial class BanManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonRequired, JsonPropertyName("ban_id")]
|
[JsonRequired, JsonPropertyName("ban_id")]
|
||||||
public int BanId { get; init; }
|
public int BanId { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The id of the server the ban was made on.
|
|
||||||
/// This is used to avoid double work checking the ban on the originating server.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is optional in case the ban was made outside a server (SS14.Admin)
|
|
||||||
/// </remarks>
|
|
||||||
[JsonPropertyName("server_id")]
|
|
||||||
public int? ServerId { get; init; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -21,6 +20,7 @@ using Robust.Shared.Network;
|
|||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Managers;
|
namespace Content.Server.Administration.Managers;
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
public const string SawmillId = "admin.bans";
|
public const string SawmillId = "admin.bans";
|
||||||
public const string PrefixAntag = "Antag:";
|
public const string DbTypeAntag = "Antag";
|
||||||
public const string PrefixJob = "Job:";
|
public const string DbTypeJob = "Job";
|
||||||
|
|
||||||
private readonly Dictionary<ICommonSession, List<ServerRoleBanDef>> _cachedRoleBans = new();
|
private readonly Dictionary<ICommonSession, List<BanDef>> _cachedRoleBans = new();
|
||||||
// Cached ban exemption flags are used to handle
|
// Cached ban exemption flags are used to handle
|
||||||
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
|
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
|
||||||
|
|
||||||
@@ -72,9 +72,15 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
var netChannel = player.Channel;
|
var netChannel = player.Channel;
|
||||||
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
|
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
|
||||||
var modernHwids = netChannel.UserData.ModernHWIds;
|
var modernHwids = netChannel.UserData.ModernHWIds;
|
||||||
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, modernHwids, false);
|
var roleBans = await _db.GetBansAsync(
|
||||||
|
netChannel.RemoteEndPoint.Address,
|
||||||
|
player.UserId,
|
||||||
|
hwId,
|
||||||
|
modernHwids,
|
||||||
|
false,
|
||||||
|
type: BanType.Role);
|
||||||
|
|
||||||
var userRoleBans = new List<ServerRoleBanDef>();
|
var userRoleBans = new List<BanDef>();
|
||||||
foreach (var ban in roleBans)
|
foreach (var ban in roleBans)
|
||||||
{
|
{
|
||||||
userRoleBans.Add(ban);
|
userRoleBans.Add(ban);
|
||||||
@@ -115,43 +121,37 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Server Bans
|
#region Server Bans
|
||||||
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason)
|
public async void CreateServerBan(CreateServerBanInfo banInfo)
|
||||||
{
|
{
|
||||||
DateTimeOffset? expires = null;
|
var (banDef, expires) = await CreateBanDef(banInfo, BanType.Server, null);
|
||||||
if (minutes > 0)
|
|
||||||
|
await _db.AddBanAsync(banDef);
|
||||||
|
|
||||||
|
if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules))
|
||||||
{
|
{
|
||||||
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
|
// Reset their last read rules. They probably need a refresher!
|
||||||
|
foreach (var (userId, _) in banInfo.Users)
|
||||||
|
{
|
||||||
|
await _db.SetLastReadRules(userId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_systems.TryGetEntitySystem<GameTicker>(out var ticker);
|
var adminName = banInfo.BanningAdmin == null
|
||||||
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
|
|
||||||
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
|
|
||||||
|
|
||||||
var banDef = new ServerBanDef(
|
|
||||||
null,
|
|
||||||
target,
|
|
||||||
addressRange,
|
|
||||||
hwid,
|
|
||||||
DateTimeOffset.Now,
|
|
||||||
expires,
|
|
||||||
roundId,
|
|
||||||
playtime,
|
|
||||||
reason,
|
|
||||||
severity,
|
|
||||||
banningAdmin,
|
|
||||||
null);
|
|
||||||
|
|
||||||
await _db.AddServerBanAsync(banDef);
|
|
||||||
if (_cfg.GetCVar(CCVars.ServerBanResetLastReadRules) && target != null)
|
|
||||||
await _db.SetLastReadRules(target.Value, null); // Reset their last read rules. They probably need a refresher!
|
|
||||||
var adminName = banningAdmin == null
|
|
||||||
? Loc.GetString("system-user")
|
? Loc.GetString("system-user")
|
||||||
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
|
: (await _db.GetPlayerRecordByUserId(banInfo.BanningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||||
var targetName = target is null ? "null" : $"{targetUsername} ({target})";
|
|
||||||
var addressRangeString = addressRange != null
|
var targetName = banInfo.Users.Count == 0
|
||||||
? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
|
? "null"
|
||||||
: "null";
|
: string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})"));
|
||||||
var hwidString = hwid?.ToString() ?? "null";
|
|
||||||
|
var addressRangeString = banInfo.AddressRanges.Count != 0
|
||||||
|
? "null"
|
||||||
|
: string.Join(", ", banInfo.AddressRanges.Select(a => $"{a.Address}/{a.Mask}"));
|
||||||
|
|
||||||
|
var hwidString = banInfo.HWIds.Count == 0
|
||||||
|
? "null"
|
||||||
|
: string.Join(", ", banInfo.HWIds);
|
||||||
|
|
||||||
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
|
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
|
||||||
|
|
||||||
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
|
var key = _cfg.GetCVar(CCVars.AdminShowPIIOnBan) ? "server-ban-string" : "server-ban-string-no-pii";
|
||||||
@@ -159,12 +159,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
var logMessage = Loc.GetString(
|
var logMessage = Loc.GetString(
|
||||||
key,
|
key,
|
||||||
("admin", adminName),
|
("admin", adminName),
|
||||||
("severity", severity),
|
("severity", banDef.Severity),
|
||||||
("expires", expiresString),
|
("expires", expiresString),
|
||||||
("name", targetName),
|
("name", targetName),
|
||||||
("ip", addressRangeString),
|
("ip", addressRangeString),
|
||||||
("hwid", hwidString),
|
("hwid", hwidString),
|
||||||
("reason", reason));
|
("reason", banInfo.Reason));
|
||||||
|
|
||||||
_sawmill.Info(logMessage);
|
_sawmill.Info(logMessage);
|
||||||
_chat.SendAdminAlert(logMessage);
|
_chat.SendAdminAlert(logMessage);
|
||||||
@@ -172,7 +172,19 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
KickMatchingConnectedPlayers(banDef, "newly placed ban");
|
KickMatchingConnectedPlayers(banDef, "newly placed ban");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KickMatchingConnectedPlayers(ServerBanDef def, string source)
|
private NoteSeverity GetSeverityForServerBan(CreateBanInfo banInfo, CVarDef<string> defaultCVar)
|
||||||
|
{
|
||||||
|
if (banInfo.Severity != null)
|
||||||
|
return banInfo.Severity.Value;
|
||||||
|
|
||||||
|
if (Enum.TryParse(_cfg.GetCVar(defaultCVar), true, out NoteSeverity parsedSeverity))
|
||||||
|
return parsedSeverity;
|
||||||
|
|
||||||
|
_sawmill.Error($"CVar {defaultCVar.Name} has invalid ban severity!");
|
||||||
|
return NoteSeverity.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void KickMatchingConnectedPlayers(BanDef def, string source)
|
||||||
{
|
{
|
||||||
foreach (var player in _playerManager.Sessions)
|
foreach (var player in _playerManager.Sessions)
|
||||||
{
|
{
|
||||||
@@ -184,7 +196,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban)
|
private bool BanMatchesPlayer(ICommonSession player, BanDef ban)
|
||||||
{
|
{
|
||||||
var playerInfo = new BanMatcher.PlayerInfo
|
var playerInfo = new BanMatcher.PlayerInfo
|
||||||
{
|
{
|
||||||
@@ -201,7 +213,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
return BanMatcher.BanMatches(ban, playerInfo);
|
return BanMatcher.BanMatches(ban, playerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KickForBanDef(ICommonSession player, ServerBanDef def)
|
private void KickForBanDef(ICommonSession player, BanDef def)
|
||||||
{
|
{
|
||||||
var message = def.FormatBanMessage(_cfg, _localizationManager);
|
var message = def.FormatBanMessage(_cfg, _localizationManager);
|
||||||
player.Channel.Disconnect(message);
|
player.Channel.Disconnect(message);
|
||||||
@@ -211,108 +223,154 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
|
|
||||||
#region Role Bans
|
#region Role Bans
|
||||||
|
|
||||||
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
|
public async void CreateRoleBan(CreateRoleBanInfo banInfo)
|
||||||
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
|
|
||||||
public async void CreateRoleBan<T>(
|
|
||||||
NetUserId? target,
|
|
||||||
string? targetUsername,
|
|
||||||
NetUserId? banningAdmin,
|
|
||||||
(IPAddress, int)? addressRange,
|
|
||||||
ImmutableTypedHwid? hwid,
|
|
||||||
ProtoId<T> role,
|
|
||||||
uint? minutes,
|
|
||||||
NoteSeverity severity,
|
|
||||||
string reason,
|
|
||||||
DateTimeOffset timeOfBan
|
|
||||||
) where T : class, IPrototype
|
|
||||||
{
|
{
|
||||||
string encodedRole;
|
ImmutableArray<BanRoleDef> roleDefs =
|
||||||
|
[
|
||||||
|
.. ToBanRoleDef(banInfo.JobPrototypes),
|
||||||
|
.. ToBanRoleDef(banInfo.AntagPrototypes),
|
||||||
|
];
|
||||||
|
|
||||||
// TODO: Note that it's possible to clash IDs here between a job and an antag. The refactor that introduced
|
if (roleDefs.Length == 0)
|
||||||
// this check has consciously avoided refactoring Job and Antag prototype.
|
throw new ArgumentException("Must specify at least one role to ban!");
|
||||||
// Refactor Job- and Antag- Prototype to introduce a common RolePrototype, which will fix this possible clash.
|
|
||||||
|
|
||||||
//TODO remove this check as part of the above refactor
|
var (banDef, expires) = await CreateBanDef(banInfo, BanType.Role, roleDefs);
|
||||||
if (_prototypeManager.HasIndex<JobPrototype>(role) && _prototypeManager.HasIndex<AntagPrototype>(role))
|
|
||||||
|
await AddRoleBan(banDef);
|
||||||
|
|
||||||
|
var length = expires == null
|
||||||
|
? Loc.GetString("cmd-roleban-inf")
|
||||||
|
: Loc.GetString("cmd-roleban-until", ("expires", expires));
|
||||||
|
|
||||||
|
var targetName = banInfo.Users.Count == 0
|
||||||
|
? "null"
|
||||||
|
: string.Join(", ", banInfo.Users.Select(u => $"{u.UserName} ({u.UserId})"));
|
||||||
|
|
||||||
|
_chat.SendAdminAlert(Loc.GetString(
|
||||||
|
"cmd-roleban-success",
|
||||||
|
("target", targetName),
|
||||||
|
("role", string.Join(", ", roleDefs)),
|
||||||
|
("reason", banInfo.Reason),
|
||||||
|
("length", length)));
|
||||||
|
|
||||||
|
foreach (var (userId, _) in banInfo.Users)
|
||||||
{
|
{
|
||||||
_sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is both JobPrototype and AntagPrototype.");
|
if (_playerManager.TryGetSessionById(userId, out var session))
|
||||||
|
SendRoleBans(session);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't trust the input: make sure the job or antag actually exists.
|
|
||||||
if (_prototypeManager.HasIndex<JobPrototype>(role))
|
|
||||||
encodedRole = PrefixJob + role;
|
|
||||||
else if (_prototypeManager.HasIndex<AntagPrototype>(role))
|
|
||||||
encodedRole = PrefixAntag + role;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sawmill.Error($"Creating role ban for {role}: cannot create role ban, role is not a JobPrototype or an AntagPrototype.");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTimeOffset? expires = null;
|
|
||||||
|
|
||||||
if (minutes > 0)
|
|
||||||
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
|
|
||||||
|
|
||||||
_systems.TryGetEntitySystem(out GameTicker? ticker);
|
|
||||||
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
|
|
||||||
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
|
|
||||||
|
|
||||||
var banDef = new ServerRoleBanDef(
|
|
||||||
null,
|
|
||||||
target,
|
|
||||||
addressRange,
|
|
||||||
hwid,
|
|
||||||
timeOfBan,
|
|
||||||
expires,
|
|
||||||
roundId,
|
|
||||||
playtime,
|
|
||||||
reason,
|
|
||||||
severity,
|
|
||||||
banningAdmin,
|
|
||||||
null,
|
|
||||||
encodedRole);
|
|
||||||
|
|
||||||
if (!await AddRoleBan(banDef))
|
|
||||||
{
|
|
||||||
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role)));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
|
|
||||||
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
|
|
||||||
|
|
||||||
if (target is not null && _playerManager.TryGetSessionById(target.Value, out var session))
|
|
||||||
SendRoleBans(session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
|
private async Task<(BanDef Ban, DateTimeOffset? Expires)> CreateBanDef(
|
||||||
|
CreateBanInfo banInfo,
|
||||||
|
BanType type,
|
||||||
|
ImmutableArray<BanRoleDef>? roleBans)
|
||||||
{
|
{
|
||||||
banDef = await _db.AddServerRoleBanAsync(banDef);
|
if (banInfo.Users.Count == 0 && banInfo.HWIds.Count == 0 && banInfo.AddressRanges.Count == 0)
|
||||||
|
throw new ArgumentException("Must specify at least one user, HWID, or address range");
|
||||||
|
|
||||||
if (banDef.UserId != null
|
DateTimeOffset? expires = null;
|
||||||
&& _playerManager.TryGetSessionById(banDef.UserId, out var player)
|
if (banInfo.Duration is { } duration)
|
||||||
&& _cachedRoleBans.TryGetValue(player, out var cachedBans))
|
expires = DateTimeOffset.Now + duration;
|
||||||
|
|
||||||
|
ImmutableArray<int> roundIds;
|
||||||
|
if (banInfo.RoundIds.Count > 0)
|
||||||
{
|
{
|
||||||
cachedBans.Add(banDef);
|
roundIds = [..banInfo.RoundIds];
|
||||||
|
}
|
||||||
|
else if (_systems.TryGetEntitySystem<GameTicker>(out var ticker) && ticker.RoundId != 0)
|
||||||
|
{
|
||||||
|
roundIds = [ticker.RoundId];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
roundIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return (new BanDef(
|
||||||
|
null,
|
||||||
|
type,
|
||||||
|
[..banInfo.Users.Select(u => u.UserId)],
|
||||||
|
[..banInfo.AddressRanges],
|
||||||
|
[..banInfo.HWIds],
|
||||||
|
DateTimeOffset.Now,
|
||||||
|
expires,
|
||||||
|
roundIds,
|
||||||
|
await GetPlayTime(banInfo),
|
||||||
|
banInfo.Reason,
|
||||||
|
GetSeverityForServerBan(banInfo, CCVars.ServerBanDefaultSeverity),
|
||||||
|
banInfo.BanningAdmin,
|
||||||
|
null,
|
||||||
|
roles: roleBans), expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TimeSpan> GetPlayTime(CreateBanInfo banInfo)
|
||||||
|
{
|
||||||
|
var firstPlayer = banInfo.Users.FirstOrNull()?.UserId;
|
||||||
|
if (firstPlayer == null)
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
|
||||||
|
return (await _db.GetPlayTimes(firstPlayer.Value))
|
||||||
|
.Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)
|
||||||
|
?.TimeSpent ?? TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BanRoleDef> ToBanRoleDef<T>(IEnumerable<ProtoId<T>> protoIds) where T : class, IPrototype
|
||||||
|
{
|
||||||
|
return protoIds.Select(protoId =>
|
||||||
|
{
|
||||||
|
// TODO: I have no idea if this check is necessary. The previous code was a complete mess,
|
||||||
|
// so out of safety I'm leaving this in.
|
||||||
|
if (_prototypeManager.HasIndex<JobPrototype>(protoId) && _prototypeManager.HasIndex<AntagPrototype>(protoId))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Creating role ban for {protoId}: cannot create role ban, role is both JobPrototype and AntagPrototype.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't trust the input: make sure the role actually exists.
|
||||||
|
if (!_prototypeManager.HasIndex(protoId))
|
||||||
|
throw new UnknownPrototypeException(protoId, typeof(T));
|
||||||
|
|
||||||
|
return new BanRoleDef(PrototypeKindToDbType<T>(), protoId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PrototypeKindToDbType<T>() where T : class, IPrototype
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(JobPrototype))
|
||||||
|
return DbTypeJob;
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(AntagPrototype))
|
||||||
|
return DbTypeAntag;
|
||||||
|
|
||||||
|
throw new ArgumentException($"Unknown prototype kind for role bans: {typeof(T)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddRoleBan(BanDef banDef)
|
||||||
|
{
|
||||||
|
banDef = await _db.AddBanAsync(banDef);
|
||||||
|
|
||||||
|
foreach (var user in banDef.UserIds)
|
||||||
|
{
|
||||||
|
if (_playerManager.TryGetSessionById(user, out var player)
|
||||||
|
&& _cachedRoleBans.TryGetValue(player, out var cachedBans))
|
||||||
|
{
|
||||||
|
cachedBans.Add(banDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
|
public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
|
||||||
{
|
{
|
||||||
var ban = await _db.GetServerRoleBanAsync(banId);
|
var ban = await _db.GetBanAsync(banId);
|
||||||
|
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
return $"No ban found with id {banId}";
|
return $"No ban found with id {banId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ban.Type != BanType.Role)
|
||||||
|
throw new InvalidOperationException("Ban was not a role ban!");
|
||||||
|
|
||||||
if (ban.Unban != null)
|
if (ban.Unban != null)
|
||||||
{
|
{
|
||||||
var response = new StringBuilder("This ban has already been pardoned");
|
var response = new StringBuilder("This ban has already been pardoned");
|
||||||
@@ -326,14 +384,17 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
return response.ToString();
|
return response.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
|
await _db.AddUnbanAsync(new UnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
|
||||||
|
|
||||||
if (ban.UserId is { } player
|
foreach (var user in ban.UserIds)
|
||||||
&& _playerManager.TryGetSessionById(player, out var session)
|
|
||||||
&& _cachedRoleBans.TryGetValue(session, out var roleBans))
|
|
||||||
{
|
{
|
||||||
roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
|
if (_playerManager.TryGetSessionById(user, out var session)
|
||||||
SendRoleBans(session);
|
&& _cachedRoleBans.TryGetValue(session, out var roleBans))
|
||||||
|
{
|
||||||
|
roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
|
||||||
|
SendRoleBans(session);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"Pardoned ban with id {banId}";
|
return $"Pardoned ban with id {banId}";
|
||||||
@@ -341,64 +402,69 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
|
|
||||||
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
|
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
|
||||||
{
|
{
|
||||||
return GetRoleBans<JobPrototype>(playerUserId, PrefixJob);
|
return GetRoleBans<JobPrototype>(playerUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId)
|
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId)
|
||||||
{
|
{
|
||||||
return GetRoleBans<AntagPrototype>(playerUserId, PrefixAntag);
|
return GetRoleBans<AntagPrototype>(playerUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashSet<ProtoId<T>>? GetRoleBans<T>(NetUserId playerUserId, string prefix) where T : class, IPrototype
|
private HashSet<ProtoId<T>>? GetRoleBans<T>(NetUserId playerUserId) where T : class, IPrototype
|
||||||
{
|
{
|
||||||
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
|
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return GetRoleBans<T>(session, prefix);
|
return GetRoleBans<T>(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HashSet<ProtoId<T>>? GetRoleBans<T>(ICommonSession playerSession, string prefix) where T : class, IPrototype
|
private HashSet<ProtoId<T>>? GetRoleBans<T>(ICommonSession playerSession) where T : class, IPrototype
|
||||||
{
|
{
|
||||||
if (!_cachedRoleBans.TryGetValue(playerSession, out var roleBans))
|
if (!_cachedRoleBans.TryGetValue(playerSession, out var roleBans))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var dbType = PrototypeKindToDbType<T>();
|
||||||
|
|
||||||
return roleBans
|
return roleBans
|
||||||
.Where(ban => ban.Role.StartsWith(prefix, StringComparison.Ordinal))
|
.SelectMany(ban => ban.Roles!.Value)
|
||||||
.Select(ban => new ProtoId<T>(ban.Role[prefix.Length..]))
|
.Where(role => role.RoleType == dbType)
|
||||||
|
.Select(role => new ProtoId<T>(role.RoleId))
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
|
public HashSet<BanRoleDef>? GetRoleBans(NetUserId playerUserId)
|
||||||
{
|
{
|
||||||
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
|
if (!_playerManager.TryGetSessionById(playerUserId, out var session))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return _cachedRoleBans.TryGetValue(session, out var roleBans)
|
return _cachedRoleBans.TryGetValue(session, out var roleBans)
|
||||||
? roleBans.Select(banDef => banDef.Role).ToHashSet()
|
? roleBans.SelectMany(banDef => banDef.Roles ?? []).ToHashSet()
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRoleBanned(ICommonSession player, List<ProtoId<JobPrototype>> jobs)
|
public bool IsRoleBanned(ICommonSession player, List<ProtoId<JobPrototype>> jobs)
|
||||||
{
|
{
|
||||||
return IsRoleBanned(player, jobs, PrefixJob);
|
return IsRoleBanned<JobPrototype>(player, jobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRoleBanned(ICommonSession player, List<ProtoId<AntagPrototype>> antags)
|
public bool IsRoleBanned(ICommonSession player, List<ProtoId<AntagPrototype>> antags)
|
||||||
{
|
{
|
||||||
return IsRoleBanned(player, antags, PrefixAntag);
|
return IsRoleBanned<AntagPrototype>(player, antags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsRoleBanned<T>(ICommonSession player, List<ProtoId<T>> roles, string prefix) where T : class, IPrototype
|
private bool IsRoleBanned<T>(ICommonSession player, List<ProtoId<T>> roles) where T : class, IPrototype
|
||||||
{
|
{
|
||||||
var bans = GetRoleBans(player.UserId);
|
var bans = GetRoleBans(player.UserId);
|
||||||
|
|
||||||
if (bans is null || bans.Count == 0)
|
if (bans is null || bans.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
var dbType = PrototypeKindToDbType<T>();
|
||||||
|
|
||||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||||
foreach (var role in roles)
|
foreach (var role in roles)
|
||||||
{
|
{
|
||||||
if (bans.Contains(prefix + role))
|
if (bans.Contains(new BanRoleDef(dbType, role)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,34 +473,10 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
|
|||||||
|
|
||||||
public void SendRoleBans(ICommonSession pSession)
|
public void SendRoleBans(ICommonSession pSession)
|
||||||
{
|
{
|
||||||
var jobBans = GetRoleBans<JobPrototype>(pSession, PrefixJob);
|
|
||||||
var jobBansList = new List<string>(jobBans?.Count ?? 0);
|
|
||||||
|
|
||||||
if (jobBans is not null)
|
|
||||||
{
|
|
||||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
|
||||||
foreach (var encodedId in jobBans)
|
|
||||||
{
|
|
||||||
jobBansList.Add(encodedId.ToString().Replace(PrefixJob, ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var antagBans = GetRoleBans<AntagPrototype>(pSession, PrefixAntag);
|
|
||||||
var antagBansList = new List<string>(antagBans?.Count ?? 0);
|
|
||||||
|
|
||||||
if (antagBans is not null)
|
|
||||||
{
|
|
||||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
|
||||||
foreach (var encodedId in antagBans)
|
|
||||||
{
|
|
||||||
antagBansList.Add(encodedId.ToString().Replace(PrefixAntag, ""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bans = new MsgRoleBans()
|
var bans = new MsgRoleBans()
|
||||||
{
|
{
|
||||||
JobBans = jobBansList,
|
JobBans = (GetRoleBans<JobPrototype>(pSession) ?? []).ToList(),
|
||||||
AntagBans = antagBansList,
|
AntagBans = (GetRoleBans<AntagPrototype>(pSession) ?? []).ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_sawmill.Debug($"Sent role bans to {pSession.Name}");
|
_sawmill.Debug($"Sent role bans to {pSession.Name}");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -13,6 +14,11 @@ public interface IBanManager
|
|||||||
public void Initialize();
|
public void Initialize();
|
||||||
public void Restart();
|
public void Restart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a server ban in the database, blocking connection for matching players.
|
||||||
|
/// </summary>
|
||||||
|
void CreateServerBan(CreateServerBanInfo banInfo);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bans the specified target, address range and / or HWID. One of them must be non-null
|
/// Bans the specified target, address range and / or HWID. One of them must be non-null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,12 +29,44 @@ public interface IBanManager
|
|||||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
||||||
/// <param name="severity">Severity of the resulting ban note</param>
|
/// <param name="severity">Severity of the resulting ban note</param>
|
||||||
/// <param name="reason">Reason for the ban</param>
|
/// <param name="reason">Reason for the ban</param>
|
||||||
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableTypedHwid? hwid, uint? minutes, NoteSeverity severity, string reason);
|
[Obsolete("Use CreateServerBan(CreateBanInfo) instead")]
|
||||||
|
public void CreateServerBan(NetUserId? target,
|
||||||
|
string? targetUsername,
|
||||||
|
NetUserId? banningAdmin,
|
||||||
|
(IPAddress, int)? addressRange,
|
||||||
|
ImmutableTypedHwid? hwid,
|
||||||
|
uint? minutes,
|
||||||
|
NoteSeverity severity,
|
||||||
|
string reason)
|
||||||
|
{
|
||||||
|
var info = new CreateServerBanInfo(reason);
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(targetUsername);
|
||||||
|
info.AddUser(target.Value, targetUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addressRange != null)
|
||||||
|
info.AddAddressRange(addressRange.Value);
|
||||||
|
|
||||||
|
if (hwid != null)
|
||||||
|
info.AddHWId(hwid);
|
||||||
|
|
||||||
|
if (minutes > 0)
|
||||||
|
info.WithMinutes(minutes.Value);
|
||||||
|
|
||||||
|
if (banningAdmin != null)
|
||||||
|
info.WithBanningAdmin(banningAdmin.Value);
|
||||||
|
|
||||||
|
info.WithSeverity(severity);
|
||||||
|
|
||||||
|
CreateServerBan(info);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of prefixed prototype IDs with the player's role bans.
|
/// Gets a list of prefixed prototype IDs with the player's role bans.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
|
public HashSet<BanRoleDef>? GetRoleBans(NetUserId playerUserId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the player is currently banned from any of the listed roles.
|
/// Checks if the player is currently banned from any of the listed roles.
|
||||||
@@ -57,33 +95,12 @@ public interface IBanManager
|
|||||||
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId);
|
public HashSet<ProtoId<AntagPrototype>>? GetAntagBans(NetUserId playerUserId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a job ban for the specified target, username or GUID
|
/// Creates a role ban, preventing matching players from playing said roles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">Target user, username or GUID, null for none</param>
|
public void CreateRoleBan(CreateRoleBanInfo banInfo);
|
||||||
/// <param name="targetUsername">The username of the target, if known</param>
|
|
||||||
/// <param name="banningAdmin">The responsible admin for the ban</param>
|
|
||||||
/// <param name="addressRange">The range of IPs that are to be banned, if known</param>
|
|
||||||
/// <param name="hwid">The HWID to be banned, if known</param>
|
|
||||||
/// <param name="role">The role ID to be banned from. Either an AntagPrototype or a JobPrototype</param>
|
|
||||||
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
|
|
||||||
/// <param name="severity">Severity of the resulting ban note</param>
|
|
||||||
/// <param name="reason">Reason for the ban</param>
|
|
||||||
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
|
|
||||||
public void CreateRoleBan<T>(
|
|
||||||
NetUserId? target,
|
|
||||||
string? targetUsername,
|
|
||||||
NetUserId? banningAdmin,
|
|
||||||
(IPAddress, int)? addressRange,
|
|
||||||
ImmutableTypedHwid? hwid,
|
|
||||||
ProtoId<T> role,
|
|
||||||
uint? minutes,
|
|
||||||
NoteSeverity severity,
|
|
||||||
string reason,
|
|
||||||
DateTimeOffset timeOfBan
|
|
||||||
) where T : class, IPrototype;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pardons a role ban for the specified target, username or GUID
|
/// Pardons a role ban by its ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="banId">The id of the role ban to pardon.</param>
|
/// <param name="banId">The id of the role ban to pardon.</param>
|
||||||
/// <param name="unbanningAdmin">The admin, if any, that pardoned the role ban.</param>
|
/// <param name="unbanningAdmin">The admin, if any, that pardoned the role ban.</param>
|
||||||
@@ -96,3 +113,287 @@ public interface IBanManager
|
|||||||
/// <param name="pSession">Player's session</param>
|
/// <param name="pSession">Player's session</param>
|
||||||
public void SendRoleBans(ICommonSession pSession);
|
public void SendRoleBans(ICommonSession pSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base info to fill out in created ban records.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="CreateServerBanInfo"/>
|
||||||
|
/// <seealso cref="CreateRoleBanInfo"/>
|
||||||
|
[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
|
||||||
|
public abstract class CreateBanInfo
|
||||||
|
{
|
||||||
|
[Access(Other = AccessPermissions.Read)]
|
||||||
|
public const int DefaultMaskIpv4 = 32;
|
||||||
|
[Access(Other = AccessPermissions.Read)]
|
||||||
|
public const int DefaultMaskIpv6 = 64;
|
||||||
|
|
||||||
|
internal readonly HashSet<(NetUserId UserId, string UserName)> Users = [];
|
||||||
|
internal readonly HashSet<(IPAddress Address, int Mask)> AddressRanges = [];
|
||||||
|
internal readonly HashSet<ImmutableTypedHwid> HWIds = [];
|
||||||
|
internal readonly HashSet<int> RoundIds = [];
|
||||||
|
internal TimeSpan? Duration;
|
||||||
|
internal NoteSeverity? Severity;
|
||||||
|
internal string Reason;
|
||||||
|
internal NetUserId? BanningAdmin;
|
||||||
|
|
||||||
|
protected CreateBanInfo(string reason)
|
||||||
|
{
|
||||||
|
Reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a user to be matched by the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Bans can target multiple users at once.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="userId">The ID of the user.</param>
|
||||||
|
/// <param name="username">The name of the user (used for logging purposes).</param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddUser(NetUserId userId, string username)
|
||||||
|
{
|
||||||
|
Users.Add((userId, username));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an IP address to be matched by the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Bans can target multiple addresses at once.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="address">
|
||||||
|
/// The IP address to add. If null, nothing is done.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddAddress(IPAddress? address)
|
||||||
|
{
|
||||||
|
if (address == null)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return AddAddressRange(
|
||||||
|
address,
|
||||||
|
address.AddressFamily == AddressFamily.InterNetwork ? DefaultMaskIpv4 : DefaultMaskIpv6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an IP address range to be matched by the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Bans can target multiple address ranges at once.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddAddressRange((IPAddress Address, int Mask) addressRange)
|
||||||
|
{
|
||||||
|
return AddAddressRange(addressRange.Address, addressRange.Mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an IP address range to be matched by the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Bans can target multiple address ranges at once.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddAddressRange(IPAddress address, int mask)
|
||||||
|
{
|
||||||
|
AddressRanges.Add((address, mask));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a hardware IP (HWID) to be matched by the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Bans can target multiple HWIDs at once.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="hwId">
|
||||||
|
/// The HWID to add. If null, nothing is done.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddHWId(ImmutableTypedHwid? hwId)
|
||||||
|
{
|
||||||
|
if (hwId != null)
|
||||||
|
HWIds.Add(hwId);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a relevant round ID to this ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// If not specified, the current round ID is used for the ban.
|
||||||
|
/// Therefore, the first call to this function will <i>replace</i> the round ID,
|
||||||
|
/// and further calls will add additional round IDs.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Bans can target multiple round IDs at once.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo AddRoundId(int roundId)
|
||||||
|
{
|
||||||
|
RoundIds.Add(roundId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set how long the ban will last, in minutes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If no duration is specified, the ban is permanent.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="minutes">The duration of the ban, in minutes.</param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown if <see cref="minutes"/> is not a positive number.
|
||||||
|
/// </exception>
|
||||||
|
public CreateBanInfo WithMinutes(int minutes)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes);
|
||||||
|
return WithMinutes((uint)minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set how long the ban will last, in minutes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If no duration is specified, the ban is permanent.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="minutes">The duration of the ban, in minutes.</param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown if <see cref="minutes"/> is not a positive number.
|
||||||
|
/// </exception>
|
||||||
|
public CreateBanInfo WithMinutes(uint minutes)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minutes);
|
||||||
|
return WithDuration(TimeSpan.FromMinutes(minutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set how long the ban will last.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If no duration is specified, the ban is permanent.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="duration">The duration of the ban.</param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown if <see cref="duration"/> is not a positive amount of time.
|
||||||
|
/// </exception>
|
||||||
|
public CreateBanInfo WithDuration(TimeSpan duration)
|
||||||
|
{
|
||||||
|
if (duration <= TimeSpan.Zero)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(duration), "Duration must be greater than zero.");
|
||||||
|
|
||||||
|
Duration = duration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the severity of the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If no severity is specified, the default is specified through server configuration.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="severity"></param>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo WithSeverity(NoteSeverity severity)
|
||||||
|
{
|
||||||
|
Severity = severity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the reason for the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This replaces the value given via the object constructor.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo WithReason(string reason)
|
||||||
|
{
|
||||||
|
Reason = reason;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify the admin responsible for placing the ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateBanInfo WithBanningAdmin(NetUserId? banningAdmin)
|
||||||
|
{
|
||||||
|
BanningAdmin = banningAdmin;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores info to create server ban records.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IBanManager.CreateServerBan(CreateServerBanInfo)"/>
|
||||||
|
[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
|
||||||
|
public sealed class CreateServerBanInfo : CreateBanInfo
|
||||||
|
{
|
||||||
|
/// <param name="reason">The reason for the server ban.</param>
|
||||||
|
public CreateServerBanInfo(string reason) : base(reason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores info to create role ban records.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="IBanManager.CreateRoleBan(CreateRoleBanInfo)"/>
|
||||||
|
[Access(typeof(BanManager), Other = AccessPermissions.Execute)]
|
||||||
|
public sealed class CreateRoleBanInfo : CreateBanInfo
|
||||||
|
{
|
||||||
|
internal readonly HashSet<ProtoId<AntagPrototype>> AntagPrototypes = [];
|
||||||
|
internal readonly HashSet<ProtoId<JobPrototype>> JobPrototypes = [];
|
||||||
|
|
||||||
|
/// <param name="reason">The reason for the role ban.</param>
|
||||||
|
public CreateRoleBanInfo(string reason) : base(reason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an antag role that will be unavailable for banned players.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Bans can have multiple roles at once.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// While not checked in this function, adding a ban with invalid role IDs will cause a
|
||||||
|
/// <see cref="UnknownPrototypeException"/> when actually creating the ban.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateRoleBanInfo AddAntag(ProtoId<AntagPrototype> protoId)
|
||||||
|
{
|
||||||
|
AntagPrototypes.Add(protoId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a job role that will be unavailable for banned players.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Bans can have multiple roles at once.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// While not checked in this function, adding a ban with invalid role IDs will cause a
|
||||||
|
/// <see cref="UnknownPrototypeException"/> when actually creating the ban.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The current object, for easy chaining.</returns>
|
||||||
|
public CreateRoleBanInfo AddJob(ProtoId<JobPrototype> protoId)
|
||||||
|
{
|
||||||
|
JobPrototypes.Add(protoId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public sealed class AdminNotesEui : BaseEui
|
|||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid NotedPlayer { get; set; }
|
private NetUserId NotedPlayer { get; set; }
|
||||||
private string NotedPlayerName { get; set; } = string.Empty;
|
private string NotedPlayerName { get; set; } = string.Empty;
|
||||||
private bool HasConnectedBefore { get; set; }
|
private bool HasConnectedBefore { get; set; }
|
||||||
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
|
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
|
||||||
@@ -112,7 +112,7 @@ public sealed class AdminNotesEui : BaseEui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ChangeNotedPlayer(Guid notedPlayer)
|
public async Task ChangeNotedPlayer(NetUserId notedPlayer)
|
||||||
{
|
{
|
||||||
NotedPlayer = notedPlayer;
|
NotedPlayer = notedPlayer;
|
||||||
await LoadFromDb();
|
await LoadFromDb();
|
||||||
@@ -120,7 +120,7 @@ public sealed class AdminNotesEui : BaseEui
|
|||||||
|
|
||||||
private void NoteModified(SharedAdminNote note)
|
private void NoteModified(SharedAdminNote note)
|
||||||
{
|
{
|
||||||
if (note.Player != NotedPlayer)
|
if (!note.Players.Contains(NotedPlayer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Notes[(note.Id, note.NoteType)] = note;
|
Notes[(note.Id, note.NoteType)] = note;
|
||||||
@@ -129,7 +129,7 @@ public sealed class AdminNotesEui : BaseEui
|
|||||||
|
|
||||||
private void NoteDeleted(SharedAdminNote note)
|
private void NoteDeleted(SharedAdminNote note)
|
||||||
{
|
{
|
||||||
if (note.Player != NotedPlayer)
|
if (!note.Players.Contains(NotedPlayer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Notes.Remove((note.Id, note.NoteType));
|
Notes.Remove((note.Id, note.NoteType));
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Shared.Administration.Notes;
|
using Content.Shared.Administration.Notes;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -11,7 +13,7 @@ public static class AdminNotesExtensions
|
|||||||
NoteSeverity? severity = null;
|
NoteSeverity? severity = null;
|
||||||
var secret = false;
|
var secret = false;
|
||||||
NoteType type;
|
NoteType type;
|
||||||
string[]? bannedRoles = null;
|
ImmutableArray<BanRoleDef>? bannedRoles = null;
|
||||||
string? unbannedByName = null;
|
string? unbannedByName = null;
|
||||||
DateTime? unbannedTime = null;
|
DateTime? unbannedTime = null;
|
||||||
bool? seen = null;
|
bool? seen = null;
|
||||||
@@ -30,13 +32,13 @@ public static class AdminNotesExtensions
|
|||||||
type = NoteType.Message;
|
type = NoteType.Message;
|
||||||
seen = adminMessage.Seen;
|
seen = adminMessage.Seen;
|
||||||
break;
|
break;
|
||||||
case ServerBanNoteRecord ban:
|
case BanNoteRecord { Type: BanType.Server } ban:
|
||||||
type = NoteType.ServerBan;
|
type = NoteType.ServerBan;
|
||||||
severity = ban.Severity;
|
severity = ban.Severity;
|
||||||
unbannedTime = ban.UnbanTime;
|
unbannedTime = ban.UnbanTime;
|
||||||
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
|
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
|
||||||
break;
|
break;
|
||||||
case ServerRoleBanNoteRecord roleBan:
|
case BanNoteRecord { Type: BanType.Role } roleBan:
|
||||||
type = NoteType.RoleBan;
|
type = NoteType.RoleBan;
|
||||||
severity = roleBan.Severity;
|
severity = roleBan.Severity;
|
||||||
bannedRoles = roleBan.Roles;
|
bannedRoles = roleBan.Roles;
|
||||||
@@ -48,14 +50,14 @@ public static class AdminNotesExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// There may be bans without a user, but why would we ever be converting them to shared notes?
|
// There may be bans without a user, but why would we ever be converting them to shared notes?
|
||||||
if (note.Player is null)
|
if (note.Players.Length == 0)
|
||||||
throw new ArgumentNullException(nameof(note), "Player user ID cannot be null for a note");
|
throw new ArgumentNullException(nameof(note), "Player user ID cannot be empty for a note");
|
||||||
|
|
||||||
return new SharedAdminNote(
|
return new SharedAdminNote(
|
||||||
note.Id,
|
note.Id,
|
||||||
note.Player!.UserId,
|
[..note.Players.Select(p => p.UserId)],
|
||||||
note.Round?.Id,
|
[..note.Rounds.Select(r => r.Id)],
|
||||||
note.Round?.Server.Name,
|
note.Rounds.SingleOrDefault()?.Server.Name, // TODO: Show all server names?
|
||||||
note.PlaytimeAtNote,
|
note.PlaytimeAtNote,
|
||||||
type,
|
type,
|
||||||
note.Message,
|
note.Message,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes);
|
return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OpenEui(ICommonSession admin, Guid notedPlayer)
|
public async Task OpenEui(ICommonSession admin, NetUserId notedPlayer)
|
||||||
{
|
{
|
||||||
var ui = new AdminNotesEui();
|
var ui = new AdminNotesEui();
|
||||||
_euis.OpenEui(ui, admin);
|
_euis.OpenEui(ui, admin);
|
||||||
@@ -144,8 +144,8 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
|
|
||||||
var note = new SharedAdminNote(
|
var note = new SharedAdminNote(
|
||||||
noteId,
|
noteId,
|
||||||
(NetUserId) player,
|
[(NetUserId) player],
|
||||||
roundId,
|
roundId.HasValue ? [roundId.Value] : [],
|
||||||
serverName,
|
serverName,
|
||||||
playtime,
|
playtime,
|
||||||
type,
|
type,
|
||||||
@@ -172,8 +172,7 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
|
NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
|
||||||
NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
|
NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
|
||||||
NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
|
NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
|
||||||
NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(),
|
NoteType.ServerBan or NoteType.RoleBan => (await _db.GetBanAsNoteAsync(id))?.ToShared(),
|
||||||
NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
|
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -200,11 +199,8 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
case NoteType.Message:
|
case NoteType.Message:
|
||||||
await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
|
await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
|
||||||
break;
|
break;
|
||||||
case NoteType.ServerBan:
|
case NoteType.ServerBan or NoteType.RoleBan:
|
||||||
await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt);
|
await _db.HideBanFromNotes(noteId, deletedBy.UserId, deletedAt);
|
||||||
break;
|
|
||||||
case NoteType.RoleBan:
|
|
||||||
await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||||
@@ -280,15 +276,10 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
|||||||
case NoteType.Message:
|
case NoteType.Message:
|
||||||
await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
|
await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
|
||||||
break;
|
break;
|
||||||
case NoteType.ServerBan:
|
case NoteType.ServerBan or NoteType.RoleBan:
|
||||||
if (severity is null)
|
if (severity is null)
|
||||||
throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
|
throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
|
||||||
await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
|
await _db.EditBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
|
||||||
break;
|
|
||||||
case NoteType.RoleBan:
|
|
||||||
if (severity is null)
|
|
||||||
throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity));
|
|
||||||
await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Shared.Administration.Notes;
|
using Content.Shared.Administration.Notes;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Notes;
|
namespace Content.Server.Administration.Notes;
|
||||||
@@ -16,7 +17,7 @@ public interface IAdminNotesManager
|
|||||||
bool CanDelete(ICommonSession admin);
|
bool CanDelete(ICommonSession admin);
|
||||||
bool CanEdit(ICommonSession admin);
|
bool CanEdit(ICommonSession admin);
|
||||||
bool CanView(ICommonSession admin);
|
bool CanView(ICommonSession admin);
|
||||||
Task OpenEui(ICommonSession admin, Guid notedPlayer);
|
Task OpenEui(ICommonSession admin, NetUserId notedPlayer);
|
||||||
Task OpenUserNotesEui(ICommonSession player);
|
Task OpenUserNotesEui(ICommonSession player);
|
||||||
Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
|
Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
|
||||||
Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession deletedBy);
|
Task DeleteAdminRemark(int noteId, NoteType type, ICommonSession deletedBy);
|
||||||
|
|||||||
@@ -186,11 +186,8 @@ public sealed class PlayerPanelEui : BaseEui
|
|||||||
{
|
{
|
||||||
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
|
_whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
|
||||||
// This won't get associated ip or hwid bans but they were not placed on this account anyways
|
// This won't get associated ip or hwid bans but they were not placed on this account anyways
|
||||||
_bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null, null)).Count;
|
_bans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null)).Count;
|
||||||
// Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
|
_roleBans = (await _db.GetBansAsync(null, _targetPlayer.UserId, null, null, type: BanType.Role)).Count();
|
||||||
// The only way to distinguish whether a role ban is the same is to compare the ban time.
|
|
||||||
// This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
|
|
||||||
_roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null, null)).DistinctBy(rb => rb.BanTime).Count();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ namespace Content.Server.Administration.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has been banned
|
// Check if the user has been banned
|
||||||
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null, null);
|
var ban = await _dbManager.GetBanAsync(null, e.Session.UserId, null, null);
|
||||||
if (ban != null)
|
if (ban != null)
|
||||||
{
|
{
|
||||||
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
|
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ namespace Content.Server.Connection
|
|||||||
* TODO: Jesus H Christ what is this utter mess of a function
|
* TODO: Jesus H Christ what is this utter mess of a function
|
||||||
* TODO: Break this apart into is constituent steps.
|
* TODO: Break this apart into is constituent steps.
|
||||||
*/
|
*/
|
||||||
private async Task<(ConnectionDenyReason, string, List<ServerBanDef>? bansHit)?> ShouldDeny(
|
private async Task<(ConnectionDenyReason, string, List<BanDef>? bansHit)?> ShouldDeny(
|
||||||
NetConnectingArgs e)
|
NetConnectingArgs e)
|
||||||
{
|
{
|
||||||
// Check if banned.
|
// Check if banned.
|
||||||
@@ -228,7 +228,7 @@ namespace Content.Server.Connection
|
|||||||
return (ConnectionDenyReason.NoHwid, Loc.GetString("hwid-required"), null);
|
return (ConnectionDenyReason.NoHwid, Loc.GetString("hwid-required"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
|
var bans = await _db.GetBansAsync(addr, userId, hwId, modernHwid, includeUnbanned: false);
|
||||||
if (bans.Count > 0)
|
if (bans.Count > 0)
|
||||||
{
|
{
|
||||||
var firstBan = bans[0];
|
var firstBan = bans[0];
|
||||||
|
|||||||
128
Content.Server/Database/BanDef.cs
Normal file
128
Content.Server/Database/BanDef.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Content.Server.Database
|
||||||
|
{
|
||||||
|
public sealed class BanDef
|
||||||
|
{
|
||||||
|
public int? Id { get; }
|
||||||
|
public BanType Type { get; }
|
||||||
|
public ImmutableArray<NetUserId> UserIds { get; }
|
||||||
|
public ImmutableArray<(IPAddress address, int cidrMask)> Addresses { get; }
|
||||||
|
public ImmutableArray<ImmutableTypedHwid> HWIds { get; }
|
||||||
|
|
||||||
|
public DateTimeOffset BanTime { get; }
|
||||||
|
public DateTimeOffset? ExpirationTime { get; }
|
||||||
|
public ImmutableArray<int> RoundIds { get; }
|
||||||
|
public TimeSpan PlaytimeAtNote { get; }
|
||||||
|
public string Reason { get; }
|
||||||
|
public NoteSeverity Severity { get; set; }
|
||||||
|
public NetUserId? BanningAdmin { get; }
|
||||||
|
public UnbanDef? Unban { get; }
|
||||||
|
public ServerBanExemptFlags ExemptFlags { get; }
|
||||||
|
|
||||||
|
public ImmutableArray<BanRoleDef>? Roles { get; }
|
||||||
|
|
||||||
|
public BanDef(
|
||||||
|
int? id,
|
||||||
|
BanType type,
|
||||||
|
ImmutableArray<NetUserId> userIds,
|
||||||
|
ImmutableArray<(IPAddress address, int cidrMask)> addresses,
|
||||||
|
ImmutableArray<ImmutableTypedHwid> hwIds,
|
||||||
|
DateTimeOffset banTime,
|
||||||
|
DateTimeOffset? expirationTime,
|
||||||
|
ImmutableArray<int> roundIds,
|
||||||
|
TimeSpan playtimeAtNote,
|
||||||
|
string reason,
|
||||||
|
NoteSeverity severity,
|
||||||
|
NetUserId? banningAdmin,
|
||||||
|
UnbanDef? unban,
|
||||||
|
ServerBanExemptFlags exemptFlags = default,
|
||||||
|
ImmutableArray<BanRoleDef>? roles = null)
|
||||||
|
{
|
||||||
|
if (userIds.Length == 0 && addresses.Length == 0 && hwIds.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses = addresses.Select(address =>
|
||||||
|
{
|
||||||
|
if (address is { address.IsIPv4MappedToIPv6: true } addr)
|
||||||
|
{
|
||||||
|
// Fix IPv6-mapped IPv4 addresses
|
||||||
|
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
|
||||||
|
address = (addr.address.MapToIPv4(), addr.cidrMask - 96);
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
})
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
Id = id;
|
||||||
|
Type = type;
|
||||||
|
UserIds = userIds;
|
||||||
|
Addresses = addresses;
|
||||||
|
HWIds = hwIds;
|
||||||
|
BanTime = banTime;
|
||||||
|
ExpirationTime = expirationTime;
|
||||||
|
RoundIds = roundIds;
|
||||||
|
PlaytimeAtNote = playtimeAtNote;
|
||||||
|
Reason = reason;
|
||||||
|
Severity = severity;
|
||||||
|
BanningAdmin = banningAdmin;
|
||||||
|
Unban = unban;
|
||||||
|
ExemptFlags = exemptFlags;
|
||||||
|
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case BanType.Server:
|
||||||
|
if (roles != null)
|
||||||
|
throw new ArgumentException("Cannot specify roles for server ban types", nameof(roles));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BanType.Role:
|
||||||
|
if (roles is not { Length: > 0 })
|
||||||
|
throw new ArgumentException("Must specify roles for server ban types", nameof(roles));
|
||||||
|
if (exemptFlags != 0)
|
||||||
|
throw new ArgumentException("Role bans cannot have exempt flags", nameof(exemptFlags));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
Roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc)
|
||||||
|
{
|
||||||
|
string expires;
|
||||||
|
if (ExpirationTime is { } expireTime)
|
||||||
|
{
|
||||||
|
var duration = expireTime - BanTime;
|
||||||
|
var utc = expireTime.ToUniversalTime();
|
||||||
|
expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal);
|
||||||
|
expires = !string.IsNullOrWhiteSpace(appeal)
|
||||||
|
? loc.GetString("ban-banned-permanent-appeal", ("link", appeal))
|
||||||
|
: loc.GetString("ban-banned-permanent");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"""
|
||||||
|
{loc.GetString("ban-banned-1")}
|
||||||
|
{loc.GetString("ban-banned-2", ("reason", Reason))}
|
||||||
|
{expires}
|
||||||
|
{loc.GetString("ban-banned-3")}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Content.Server.IP;
|
using Content.Server.IP;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -7,7 +8,7 @@ using Robust.Shared.Network;
|
|||||||
namespace Content.Server.Database;
|
namespace Content.Server.Database;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements logic to match a <see cref="ServerBanDef"/> against a player query.
|
/// Implements logic to match a <see cref="BanDef"/> against a player query.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
@@ -29,7 +30,7 @@ public static class BanMatcher
|
|||||||
/// <param name="ban">The ban information.</param>
|
/// <param name="ban">The ban information.</param>
|
||||||
/// <param name="player">Information about the player to match against.</param>
|
/// <param name="player">Information about the player to match against.</param>
|
||||||
/// <returns>True if the ban matches the provided player info.</returns>
|
/// <returns>True if the ban matches the provided player info.</returns>
|
||||||
public static bool BanMatches(ServerBanDef ban, in PlayerInfo player)
|
public static bool BanMatches(BanDef ban, in PlayerInfo player)
|
||||||
{
|
{
|
||||||
var exemptFlags = player.ExemptFlags;
|
var exemptFlags = player.ExemptFlags;
|
||||||
// Any flag to bypass BlacklistedRange bans.
|
// Any flag to bypass BlacklistedRange bans.
|
||||||
@@ -39,39 +40,44 @@ public static class BanMatcher
|
|||||||
if ((ban.ExemptFlags & exemptFlags) != 0)
|
if ((ban.ExemptFlags & exemptFlags) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
var playerAddr = player.Address;
|
||||||
if (!player.ExemptFlags.HasFlag(ServerBanExemptFlags.IP)
|
if (!player.ExemptFlags.HasFlag(ServerBanExemptFlags.IP)
|
||||||
&& player.Address != null
|
&& playerAddr != null
|
||||||
&& ban.Address is not null
|
&& ban.Addresses.Any(addr => playerAddr.IsInSubnet(addr))
|
||||||
&& player.Address.IsInSubnet(ban.Address.Value)
|
|
||||||
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || player.IsNewPlayer))
|
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || player.IsNewPlayer))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.UserId is { } id && ban.UserId == id.UserId)
|
if (player.UserId is { } id && ban.UserIds.Contains(id))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ban.HWId?.Type)
|
foreach (var banHwid in ban.HWIds)
|
||||||
{
|
{
|
||||||
case HwidType.Legacy:
|
switch (banHwid.Type)
|
||||||
if (player.HWId is { Length: > 0 } hwIdVar
|
{
|
||||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
case HwidType.Legacy:
|
||||||
{
|
if (player.HWId is { Length: > 0 } hwIdVar
|
||||||
return true;
|
&& hwIdVar.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan()))
|
||||||
}
|
|
||||||
break;
|
|
||||||
case HwidType.Modern:
|
|
||||||
if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
|
|
||||||
{
|
|
||||||
foreach (var hwid in modernHwIdVar)
|
|
||||||
{
|
{
|
||||||
if (hwid.AsSpan().SequenceEqual(ban.HWId.Hwid.AsSpan()))
|
return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
case HwidType.Modern:
|
||||||
|
if (player.ModernHWIds is { Length: > 0 } modernHwIdVar)
|
||||||
|
{
|
||||||
|
foreach (var hwid in modernHwIdVar)
|
||||||
|
{
|
||||||
|
if (hwid.AsSpan().SequenceEqual(banHwid.Hwid.AsSpan()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -12,9 +13,9 @@ public interface IAdminRemarksRecord
|
|||||||
{
|
{
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
|
||||||
public RoundRecord? Round { get; }
|
public ImmutableArray<RoundRecord> Rounds { get; }
|
||||||
|
|
||||||
public PlayerRecord? Player { get; }
|
public ImmutableArray<PlayerRecord> Players { get; }
|
||||||
public TimeSpan PlaytimeAtNote { get; }
|
public TimeSpan PlaytimeAtNote { get; }
|
||||||
|
|
||||||
public string Message { get; }
|
public string Message { get; }
|
||||||
@@ -31,27 +32,11 @@ public interface IAdminRemarksRecord
|
|||||||
public bool Deleted { get; }
|
public bool Deleted { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record ServerRoleBanNoteRecord(
|
public sealed record BanNoteRecord(
|
||||||
int Id,
|
int Id,
|
||||||
RoundRecord? Round,
|
BanType Type,
|
||||||
PlayerRecord? Player,
|
ImmutableArray<RoundRecord> Rounds,
|
||||||
TimeSpan PlaytimeAtNote,
|
ImmutableArray<PlayerRecord> Players,
|
||||||
string Message,
|
|
||||||
NoteSeverity Severity,
|
|
||||||
PlayerRecord? CreatedBy,
|
|
||||||
DateTimeOffset CreatedAt,
|
|
||||||
PlayerRecord? LastEditedBy,
|
|
||||||
DateTimeOffset? LastEditedAt,
|
|
||||||
DateTimeOffset? ExpirationTime,
|
|
||||||
bool Deleted,
|
|
||||||
string[] Roles,
|
|
||||||
PlayerRecord? UnbanningAdmin,
|
|
||||||
DateTime? UnbanTime) : IAdminRemarksRecord;
|
|
||||||
|
|
||||||
public sealed record ServerBanNoteRecord(
|
|
||||||
int Id,
|
|
||||||
RoundRecord? Round,
|
|
||||||
PlayerRecord? Player,
|
|
||||||
TimeSpan PlaytimeAtNote,
|
TimeSpan PlaytimeAtNote,
|
||||||
string Message,
|
string Message,
|
||||||
NoteSeverity Severity,
|
NoteSeverity Severity,
|
||||||
@@ -62,7 +47,8 @@ public sealed record ServerBanNoteRecord(
|
|||||||
DateTimeOffset? ExpirationTime,
|
DateTimeOffset? ExpirationTime,
|
||||||
bool Deleted,
|
bool Deleted,
|
||||||
PlayerRecord? UnbanningAdmin,
|
PlayerRecord? UnbanningAdmin,
|
||||||
DateTime? UnbanTime) : IAdminRemarksRecord;
|
DateTime? UnbanTime,
|
||||||
|
ImmutableArray<BanRoleDef> Roles) : IAdminRemarksRecord;
|
||||||
|
|
||||||
public sealed record AdminNoteRecord(
|
public sealed record AdminNoteRecord(
|
||||||
int Id,
|
int Id,
|
||||||
@@ -79,7 +65,11 @@ public sealed record AdminNoteRecord(
|
|||||||
bool Deleted,
|
bool Deleted,
|
||||||
PlayerRecord? DeletedBy,
|
PlayerRecord? DeletedBy,
|
||||||
DateTimeOffset? DeletedAt,
|
DateTimeOffset? DeletedAt,
|
||||||
bool Secret) : IAdminRemarksRecord;
|
bool Secret) : IAdminRemarksRecord
|
||||||
|
{
|
||||||
|
ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
|
||||||
|
ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record AdminWatchlistRecord(
|
public sealed record AdminWatchlistRecord(
|
||||||
int Id,
|
int Id,
|
||||||
@@ -94,7 +84,11 @@ public sealed record AdminWatchlistRecord(
|
|||||||
DateTimeOffset? ExpirationTime,
|
DateTimeOffset? ExpirationTime,
|
||||||
bool Deleted,
|
bool Deleted,
|
||||||
PlayerRecord? DeletedBy,
|
PlayerRecord? DeletedBy,
|
||||||
DateTimeOffset? DeletedAt) : IAdminRemarksRecord;
|
DateTimeOffset? DeletedAt) : IAdminRemarksRecord
|
||||||
|
{
|
||||||
|
ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
|
||||||
|
ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record AdminMessageRecord(
|
public sealed record AdminMessageRecord(
|
||||||
int Id,
|
int Id,
|
||||||
@@ -111,15 +105,18 @@ public sealed record AdminMessageRecord(
|
|||||||
PlayerRecord? DeletedBy,
|
PlayerRecord? DeletedBy,
|
||||||
DateTimeOffset? DeletedAt,
|
DateTimeOffset? DeletedAt,
|
||||||
bool Seen,
|
bool Seen,
|
||||||
bool Dismissed) : IAdminRemarksRecord;
|
bool Dismissed) : IAdminRemarksRecord
|
||||||
|
{
|
||||||
|
ImmutableArray<RoundRecord> IAdminRemarksRecord.Rounds => Round != null ? [Round] : [];
|
||||||
|
ImmutableArray<PlayerRecord> IAdminRemarksRecord.Players => Player != null ? [Player] : [];
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record PlayerRecord(
|
public sealed record PlayerRecord(
|
||||||
NetUserId UserId,
|
NetUserId UserId,
|
||||||
DateTimeOffset FirstSeenTime,
|
DateTimeOffset FirstSeenTime,
|
||||||
string LastSeenUserName,
|
string LastSeenUserName,
|
||||||
DateTimeOffset LastSeenTime,
|
DateTimeOffset LastSeenTime,
|
||||||
IPAddress LastSeenAddress,
|
IPAddress? LastSeenAddress,
|
||||||
ImmutableTypedHwid? HWId);
|
ImmutableTypedHwid? HWId);
|
||||||
|
|
||||||
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
|
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
|
||||||
|
|||||||
37
Content.Server/Database/EFCoreExtensions.cs
Normal file
37
Content.Server/Database/EFCoreExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Content.Server.Database;
|
||||||
|
|
||||||
|
internal static class EFCoreExtensions
|
||||||
|
{
|
||||||
|
extension<TEntity>(IQueryable<TEntity> query) where TEntity : class
|
||||||
|
{
|
||||||
|
public IQueryable<TEntity> ApplyIncludes(
|
||||||
|
IEnumerable<Expression<Func<TEntity, object>>> properties)
|
||||||
|
{
|
||||||
|
var q = query;
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
q = q.Include(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<TEntity> ApplyIncludes<TDerived>(
|
||||||
|
IEnumerable<Expression<Func<TDerived, object>>> properties,
|
||||||
|
Expression<Func<TEntity, TDerived>> getDerived)
|
||||||
|
where TDerived : class
|
||||||
|
{
|
||||||
|
var q = query;
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
q = q.Include(getDerived).ThenInclude(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Robust.Shared.Configuration;
|
|
||||||
using Robust.Shared.Network;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Server.Database
|
|
||||||
{
|
|
||||||
public sealed class ServerBanDef
|
|
||||||
{
|
|
||||||
public int? Id { get; }
|
|
||||||
public NetUserId? UserId { get; }
|
|
||||||
public (IPAddress address, int cidrMask)? Address { get; }
|
|
||||||
public ImmutableTypedHwid? HWId { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset BanTime { get; }
|
|
||||||
public DateTimeOffset? ExpirationTime { get; }
|
|
||||||
public int? RoundId { get; }
|
|
||||||
public TimeSpan PlaytimeAtNote { get; }
|
|
||||||
public string Reason { get; }
|
|
||||||
public NoteSeverity Severity { get; set; }
|
|
||||||
public NetUserId? BanningAdmin { get; }
|
|
||||||
public ServerUnbanDef? Unban { get; }
|
|
||||||
public ServerBanExemptFlags ExemptFlags { get; }
|
|
||||||
|
|
||||||
public ServerBanDef(int? id,
|
|
||||||
NetUserId? userId,
|
|
||||||
(IPAddress, int)? address,
|
|
||||||
TypedHwid? hwId,
|
|
||||||
DateTimeOffset banTime,
|
|
||||||
DateTimeOffset? expirationTime,
|
|
||||||
int? roundId,
|
|
||||||
TimeSpan playtimeAtNote,
|
|
||||||
string reason,
|
|
||||||
NoteSeverity severity,
|
|
||||||
NetUserId? banningAdmin,
|
|
||||||
ServerUnbanDef? unban,
|
|
||||||
ServerBanExemptFlags exemptFlags = default)
|
|
||||||
{
|
|
||||||
if (userId == null && address == null && hwId == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
|
|
||||||
{
|
|
||||||
// Fix IPv6-mapped IPv4 addresses
|
|
||||||
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
|
|
||||||
address = (addr.Item1.MapToIPv4(), addr.Item2 - 96);
|
|
||||||
}
|
|
||||||
|
|
||||||
Id = id;
|
|
||||||
UserId = userId;
|
|
||||||
Address = address;
|
|
||||||
HWId = hwId;
|
|
||||||
BanTime = banTime;
|
|
||||||
ExpirationTime = expirationTime;
|
|
||||||
RoundId = roundId;
|
|
||||||
PlaytimeAtNote = playtimeAtNote;
|
|
||||||
Reason = reason;
|
|
||||||
Severity = severity;
|
|
||||||
BanningAdmin = banningAdmin;
|
|
||||||
Unban = unban;
|
|
||||||
ExemptFlags = exemptFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc)
|
|
||||||
{
|
|
||||||
string expires;
|
|
||||||
if (ExpirationTime is { } expireTime)
|
|
||||||
{
|
|
||||||
var duration = expireTime - BanTime;
|
|
||||||
var utc = expireTime.ToUniversalTime();
|
|
||||||
expires = loc.GetString("ban-expires", ("duration", duration.TotalMinutes.ToString("N0")), ("time", utc.ToString("f")));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal);
|
|
||||||
expires = !string.IsNullOrWhiteSpace(appeal)
|
|
||||||
? loc.GetString("ban-banned-permanent-appeal", ("link", appeal))
|
|
||||||
: loc.GetString("ban-banned-permanent");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"""
|
|
||||||
{loc.GetString("ban-banned-1")}
|
|
||||||
{loc.GetString("ban-banned-2", ("reason", Reason))}
|
|
||||||
{expires}
|
|
||||||
{loc.GetString("ban-banned-3")}
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Administration.Managers;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Body;
|
using Content.Shared.Body;
|
||||||
using Content.Shared.Construction.Prototypes;
|
using Content.Shared.Construction.Prototypes;
|
||||||
@@ -457,7 +457,7 @@ namespace Content.Server.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ban id to look for.</param>
|
/// <param name="id">The ban id to look for.</param>
|
||||||
/// <returns>The ban with the given id or null if none exist.</returns>
|
/// <returns>The ban with the given id or null if none exist.</returns>
|
||||||
public abstract Task<ServerBanDef?> GetServerBanAsync(int id);
|
public abstract Task<BanDef?> GetBanAsync(int id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up an user's most recent received un-pardoned ban.
|
/// Looks up an user's most recent received un-pardoned ban.
|
||||||
@@ -469,11 +469,12 @@ namespace Content.Server.Database
|
|||||||
/// <param name="hwId">The legacy HWId of the user.</param>
|
/// <param name="hwId">The legacy HWId of the user.</param>
|
||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
/// <param name="modernHWIds">The modern HWIDs 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(
|
public abstract Task<BanDef?> GetBanAsync(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
|
BanType type);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up an user's ban history.
|
/// Looks up an user's ban history.
|
||||||
@@ -486,17 +487,18 @@ namespace Content.Server.Database
|
|||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||||
/// <param name="includeUnbanned">Include pardoned and expired bans.</param>
|
/// <param name="includeUnbanned">Include pardoned and expired bans.</param>
|
||||||
/// <returns>The user's ban history.</returns>
|
/// <returns>The user's ban history.</returns>
|
||||||
public abstract Task<List<ServerBanDef>> GetServerBansAsync(
|
public abstract Task<List<BanDef>> GetBansAsync(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned);
|
bool includeUnbanned,
|
||||||
|
BanType type);
|
||||||
|
|
||||||
public abstract Task AddServerBanAsync(ServerBanDef serverBan);
|
public abstract Task<BanDef> AddBanAsync(BanDef ban);
|
||||||
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
|
public abstract Task AddUnbanAsync(UnbanDef unban);
|
||||||
|
|
||||||
public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
public async Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
|
|
||||||
@@ -559,61 +561,23 @@ namespace Content.Server.Database
|
|||||||
return flags ?? ServerBanExemptFlags.None;
|
return flags ?? ServerBanExemptFlags.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
protected static List<Expression<Func<Ban, object>>> GetBanDefIncludes(BanType? type = null)
|
||||||
|
|
||||||
#region Role Bans
|
|
||||||
/*
|
|
||||||
* ROLE BANS
|
|
||||||
*/
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a role ban by id.
|
|
||||||
/// This will return a pardoned role ban as well.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The role ban id to look for.</param>
|
|
||||||
/// <returns>The role ban with the given id or null if none exist.</returns>
|
|
||||||
public abstract Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up an user's role ban history.
|
|
||||||
/// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
|
|
||||||
/// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The IP address of the user.</param>
|
|
||||||
/// <param name="userId">The NetUserId of the user.</param>
|
|
||||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
|
||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
|
||||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
|
||||||
/// <returns>The user's role ban history.</returns>
|
|
||||||
public abstract Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
bool includeUnbanned);
|
|
||||||
|
|
||||||
public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
|
|
||||||
public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban);
|
|
||||||
|
|
||||||
public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
List<Expression<Func<Ban, object>>> list =
|
||||||
var roleBanDetails = await db.DbContext.RoleBan
|
[
|
||||||
.Where(b => b.Id == id)
|
b => b.Players!,
|
||||||
.Select(b => new { b.BanTime, b.PlayerUserId })
|
b => b.Rounds!,
|
||||||
.SingleOrDefaultAsync();
|
b => b.Hwids!,
|
||||||
|
b => b.Unban!,
|
||||||
|
b => b.Addresses!,
|
||||||
|
];
|
||||||
|
|
||||||
if (roleBanDetails == default)
|
if (type != BanType.Server)
|
||||||
return;
|
list.Add(b => b.Roles!);
|
||||||
|
|
||||||
await db.DbContext.RoleBan
|
return list;
|
||||||
.Where(b => b.BanTime == roleBanDetails.BanTime && b.PlayerUserId == roleBanDetails.PlayerUserId)
|
|
||||||
.ExecuteUpdateAsync(setters => setters
|
|
||||||
.SetProperty(b => b.Severity, severity)
|
|
||||||
.SetProperty(b => b.Reason, reason)
|
|
||||||
.SetProperty(b => b.ExpirationTime, expiration.HasValue ? expiration.Value.UtcDateTime : (DateTime?)null)
|
|
||||||
.SetProperty(b => b.LastEditedById, editedBy)
|
|
||||||
.SetProperty(b => b.LastEditedAt, editedAt.UtcDateTime)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Playtime
|
#region Playtime
|
||||||
@@ -734,6 +698,19 @@ namespace Content.Server.Database
|
|||||||
if (player == null)
|
if (player == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
return MakePlayerRecord(player.UserId, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PlayerRecord MakePlayerRecord(Guid userId, Player? player)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
// We don't have a record for this player in the database.
|
||||||
|
// This is possible, for example, when banning people that never connected to the server.
|
||||||
|
// Just return fallback data here, I guess.
|
||||||
|
return new PlayerRecord(new NetUserId(userId), default, userId.ToString(), default, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
return new PlayerRecord(
|
return new PlayerRecord(
|
||||||
new NetUserId(player.UserId),
|
new NetUserId(player.UserId),
|
||||||
new DateTimeOffset(NormalizeDatabaseTime(player.FirstSeenTime)),
|
new DateTimeOffset(NormalizeDatabaseTime(player.FirstSeenTime)),
|
||||||
@@ -757,7 +734,7 @@ namespace Content.Server.Database
|
|||||||
ConnectionDenyReason? denied,
|
ConnectionDenyReason? denied,
|
||||||
int serverId);
|
int serverId);
|
||||||
|
|
||||||
public async Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
|
public async Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
|
|
||||||
@@ -1371,81 +1348,17 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
entity.Dismissed);
|
entity.Dismissed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
public async Task<BanNoteRecord?> GetBanAsNoteAsync(int id)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
|
|
||||||
var ban = await db.DbContext.Ban
|
var ban = await BanRecordQuery(db.DbContext)
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.Include(ban => ban.Round)
|
|
||||||
.ThenInclude(r => r!.Server)
|
|
||||||
.Include(ban => ban.CreatedBy)
|
|
||||||
.Include(ban => ban.LastEditedBy)
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.SingleOrDefaultAsync(b => b.Id == id);
|
.SingleOrDefaultAsync(b => b.Id == id);
|
||||||
|
|
||||||
if (ban is null)
|
if (ban is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
|
return await MakeBanNoteRecord(db.DbContext, ban);
|
||||||
return new ServerBanNoteRecord(
|
|
||||||
ban.Id,
|
|
||||||
MakeRoundRecord(ban.Round),
|
|
||||||
MakePlayerRecord(player),
|
|
||||||
ban.PlaytimeAtNote,
|
|
||||||
ban.Reason,
|
|
||||||
ban.Severity,
|
|
||||||
MakePlayerRecord(ban.CreatedBy),
|
|
||||||
ban.BanTime,
|
|
||||||
MakePlayerRecord(ban.LastEditedBy),
|
|
||||||
ban.LastEditedAt,
|
|
||||||
ban.ExpirationTime,
|
|
||||||
ban.Hidden,
|
|
||||||
MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
|
|
||||||
? null
|
|
||||||
: await db.DbContext.Player.SingleOrDefaultAsync(p =>
|
|
||||||
p.UserId == ban.Unban.UnbanningAdmin.Value)),
|
|
||||||
ban.Unban?.UnbanTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
|
|
||||||
{
|
|
||||||
await using var db = await GetDb();
|
|
||||||
|
|
||||||
var ban = await db.DbContext.RoleBan
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.Include(ban => ban.Round)
|
|
||||||
.ThenInclude(r => r!.Server)
|
|
||||||
.Include(ban => ban.CreatedBy)
|
|
||||||
.Include(ban => ban.LastEditedBy)
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.SingleOrDefaultAsync(b => b.Id == id);
|
|
||||||
|
|
||||||
if (ban is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
|
|
||||||
var unbanningAdmin =
|
|
||||||
ban.Unban is null
|
|
||||||
? null
|
|
||||||
: await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin);
|
|
||||||
|
|
||||||
return new ServerRoleBanNoteRecord(
|
|
||||||
ban.Id,
|
|
||||||
MakeRoundRecord(ban.Round),
|
|
||||||
MakePlayerRecord(player),
|
|
||||||
ban.PlaytimeAtNote,
|
|
||||||
ban.Reason,
|
|
||||||
ban.Severity,
|
|
||||||
MakePlayerRecord(ban.CreatedBy),
|
|
||||||
ban.BanTime,
|
|
||||||
MakePlayerRecord(ban.LastEditedBy),
|
|
||||||
ban.LastEditedAt,
|
|
||||||
ban.ExpirationTime,
|
|
||||||
ban.Hidden,
|
|
||||||
new [] { ban.RoleId.Replace(BanManager.PrefixJob, null).Replace(BanManager.PrefixAntag, null) },
|
|
||||||
MakePlayerRecord(unbanningAdmin),
|
|
||||||
ban.Unban?.UnbanTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
||||||
@@ -1466,8 +1379,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
.ToListAsync()).Select(MakeAdminNoteRecord));
|
.ToListAsync()).Select(MakeAdminNoteRecord));
|
||||||
notes.AddRange(await GetActiveWatchlistsImpl(db, player));
|
notes.AddRange(await GetActiveWatchlistsImpl(db, player));
|
||||||
notes.AddRange(await GetMessagesImpl(db, player));
|
notes.AddRange(await GetMessagesImpl(db, player));
|
||||||
notes.AddRange(await GetServerBansAsNotesForUser(db, player));
|
notes.AddRange(await GetBansAsNotesForUser(db, player));
|
||||||
notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
|
|
||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
|
public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
|
||||||
@@ -1550,7 +1462,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
public async Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
|
|
||||||
@@ -1563,19 +1475,6 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
||||||
{
|
|
||||||
await using var db = await GetDb();
|
|
||||||
|
|
||||||
var roleBan = await db.DbContext.RoleBan.Where(roleBan => roleBan.Id == id).SingleAsync();
|
|
||||||
|
|
||||||
roleBan.Hidden = true;
|
|
||||||
roleBan.LastEditedById = deletedBy;
|
|
||||||
roleBan.LastEditedAt = deletedAt.UtcDateTime;
|
|
||||||
|
|
||||||
await db.DbContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<IAdminRemarksRecord>> GetVisibleAdminRemarks(Guid player)
|
public async Task<List<IAdminRemarksRecord>> GetVisibleAdminRemarks(Guid player)
|
||||||
{
|
{
|
||||||
await using var db = await GetDb();
|
await using var db = await GetDb();
|
||||||
@@ -1593,8 +1492,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
.Include(note => note.Player)
|
.Include(note => note.Player)
|
||||||
.ToListAsync()).Select(MakeAdminNoteRecord));
|
.ToListAsync()).Select(MakeAdminNoteRecord));
|
||||||
notesCol.AddRange(await GetMessagesImpl(db, player));
|
notesCol.AddRange(await GetMessagesImpl(db, player));
|
||||||
notesCol.AddRange(await GetServerBansAsNotesForUser(db, player));
|
notesCol.AddRange(await GetBansAsNotesForUser(db, player));
|
||||||
notesCol.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
|
|
||||||
return notesCol;
|
return notesCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1657,43 +1555,65 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IQueryable<Ban> BanRecordQuery(ServerDbContext dbContext)
|
||||||
|
{
|
||||||
|
return dbContext.Ban
|
||||||
|
.Include(ban => ban.Unban)
|
||||||
|
.Include(ban => ban.Rounds!)
|
||||||
|
.ThenInclude(r => r.Round)
|
||||||
|
.ThenInclude(r => r!.Server)
|
||||||
|
.Include(ban => ban.Addresses)
|
||||||
|
.Include(ban => ban.Players)
|
||||||
|
.Include(ban => ban.Roles)
|
||||||
|
.Include(ban => ban.Hwids)
|
||||||
|
.Include(ban => ban.CreatedBy)
|
||||||
|
.Include(ban => ban.LastEditedBy)
|
||||||
|
.Include(ban => ban.Unban);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BanNoteRecord> MakeBanNoteRecord(ServerDbContext dbContext, Ban ban)
|
||||||
|
{
|
||||||
|
var playerRecords = await AsyncSelect(ban.Players,
|
||||||
|
async bp => MakePlayerRecord(bp.UserId,
|
||||||
|
await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == bp.UserId)));
|
||||||
|
|
||||||
|
return new BanNoteRecord(
|
||||||
|
ban.Id,
|
||||||
|
ban.Type,
|
||||||
|
[..ban.Rounds!.Select(br => MakeRoundRecord(br.Round!))],
|
||||||
|
[..playerRecords],
|
||||||
|
ban.PlaytimeAtNote,
|
||||||
|
ban.Reason,
|
||||||
|
ban.Severity,
|
||||||
|
MakePlayerRecord(ban.CreatedBy!),
|
||||||
|
NormalizeDatabaseTime(ban.BanTime),
|
||||||
|
MakePlayerRecord(ban.LastEditedBy!),
|
||||||
|
NormalizeDatabaseTime(ban.LastEditedAt),
|
||||||
|
NormalizeDatabaseTime(ban.ExpirationTime),
|
||||||
|
ban.Hidden,
|
||||||
|
ban.Unban?.UnbanningAdmin == null
|
||||||
|
? null
|
||||||
|
: MakePlayerRecord(
|
||||||
|
ban.Unban.UnbanningAdmin.Value,
|
||||||
|
await dbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.Unban.UnbanningAdmin.Value)),
|
||||||
|
NormalizeDatabaseTime(ban.Unban?.UnbanTime),
|
||||||
|
[..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))]);
|
||||||
|
}
|
||||||
|
|
||||||
// These two are here because they get converted into notes later
|
// These two are here because they get converted into notes later
|
||||||
protected async Task<List<ServerBanNoteRecord>> GetServerBansAsNotesForUser(DbGuard db, Guid user)
|
protected async Task<List<BanNoteRecord>> GetBansAsNotesForUser(DbGuard db, Guid user)
|
||||||
{
|
{
|
||||||
// You can't group queries, as player will not always exist. When it doesn't, the
|
// You can't group queries, as player will not always exist. When it doesn't, the
|
||||||
// whole query returns nothing
|
// whole query returns nothing
|
||||||
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
|
var bans = await BanRecordQuery(db.DbContext)
|
||||||
var bans = await db.DbContext.Ban
|
.AsSplitQuery()
|
||||||
.Where(ban => ban.PlayerUserId == user && !ban.Hidden)
|
.Where(ban => ban.Players!.Any(bp => bp.UserId == user) && !ban.Hidden)
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.Include(ban => ban.Round)
|
|
||||||
.ThenInclude(r => r!.Server)
|
|
||||||
.Include(ban => ban.CreatedBy)
|
|
||||||
.Include(ban => ban.LastEditedBy)
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
|
||||||
var banNotes = new List<ServerBanNoteRecord>();
|
var banNotes = new List<BanNoteRecord>();
|
||||||
foreach (var ban in bans)
|
foreach (var ban in bans)
|
||||||
{
|
{
|
||||||
var banNote = new ServerBanNoteRecord(
|
var banNote = await MakeBanNoteRecord(db.DbContext, ban);
|
||||||
ban.Id,
|
|
||||||
MakeRoundRecord(ban.Round),
|
|
||||||
MakePlayerRecord(player),
|
|
||||||
ban.PlaytimeAtNote,
|
|
||||||
ban.Reason,
|
|
||||||
ban.Severity,
|
|
||||||
MakePlayerRecord(ban.CreatedBy),
|
|
||||||
NormalizeDatabaseTime(ban.BanTime),
|
|
||||||
MakePlayerRecord(ban.LastEditedBy),
|
|
||||||
NormalizeDatabaseTime(ban.LastEditedAt),
|
|
||||||
NormalizeDatabaseTime(ban.ExpirationTime),
|
|
||||||
ban.Hidden,
|
|
||||||
MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
|
|
||||||
? null
|
|
||||||
: await db.DbContext.Player.SingleOrDefaultAsync(
|
|
||||||
p => p.UserId == ban.Unban.UnbanningAdmin.Value)),
|
|
||||||
NormalizeDatabaseTime(ban.Unban?.UnbanTime));
|
|
||||||
|
|
||||||
banNotes.Add(banNote);
|
banNotes.Add(banNote);
|
||||||
}
|
}
|
||||||
@@ -1701,56 +1621,6 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
return banNotes;
|
return banNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<List<ServerRoleBanNoteRecord>> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user)
|
|
||||||
{
|
|
||||||
// Server side query
|
|
||||||
var bansQuery = await db.DbContext.RoleBan
|
|
||||||
.Where(ban => ban.PlayerUserId == user && !ban.Hidden)
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.Include(ban => ban.Round)
|
|
||||||
.ThenInclude(r => r!.Server)
|
|
||||||
.Include(ban => ban.CreatedBy)
|
|
||||||
.Include(ban => ban.LastEditedBy)
|
|
||||||
.Include(ban => ban.Unban)
|
|
||||||
.ToArrayAsync();
|
|
||||||
|
|
||||||
// Client side query, as EF can't do groups yet
|
|
||||||
var bansEnumerable = bansQuery
|
|
||||||
.GroupBy(ban => new { ban.BanTime, CreatedBy = (Player?)ban.CreatedBy, ban.Reason, Unbanned = ban.Unban == null })
|
|
||||||
.Select(banGroup => banGroup)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
List<ServerRoleBanNoteRecord> bans = new();
|
|
||||||
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
|
|
||||||
foreach (var banGroup in bansEnumerable)
|
|
||||||
{
|
|
||||||
var firstBan = banGroup.First();
|
|
||||||
Player? unbanningAdmin = null;
|
|
||||||
|
|
||||||
if (firstBan.Unban?.UnbanningAdmin is not null)
|
|
||||||
unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value);
|
|
||||||
|
|
||||||
bans.Add(new ServerRoleBanNoteRecord(
|
|
||||||
firstBan.Id,
|
|
||||||
MakeRoundRecord(firstBan.Round),
|
|
||||||
MakePlayerRecord(player),
|
|
||||||
firstBan.PlaytimeAtNote,
|
|
||||||
firstBan.Reason,
|
|
||||||
firstBan.Severity,
|
|
||||||
MakePlayerRecord(firstBan.CreatedBy),
|
|
||||||
NormalizeDatabaseTime(firstBan.BanTime),
|
|
||||||
MakePlayerRecord(firstBan.LastEditedBy),
|
|
||||||
NormalizeDatabaseTime(firstBan.LastEditedAt),
|
|
||||||
NormalizeDatabaseTime(firstBan.ExpirationTime),
|
|
||||||
firstBan.Hidden,
|
|
||||||
banGroup.Select(ban => ban.RoleId.Replace(BanManager.PrefixJob, null).Replace(BanManager.PrefixAntag, null)).ToArray(),
|
|
||||||
MakePlayerRecord(unbanningAdmin),
|
|
||||||
NormalizeDatabaseTime(firstBan.Unban?.UnbanTime)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return bans;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Job Whitelists
|
#region Job Whitelists
|
||||||
@@ -1922,5 +1792,19 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<IEnumerable<TResult>> AsyncSelect<T, TResult>(
|
||||||
|
IEnumerable<T>? enumerable,
|
||||||
|
Func<T, Task<TResult>> selector)
|
||||||
|
{
|
||||||
|
var results = new List<TResult>();
|
||||||
|
|
||||||
|
foreach (var item in enumerable ?? [])
|
||||||
|
{
|
||||||
|
results.Add(await selector(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [..results];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Content.Server.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ban id to look for.</param>
|
/// <param name="id">The ban id to look for.</param>
|
||||||
/// <returns>The ban with the given id or null if none exist.</returns>
|
/// <returns>The ban with the given id or null if none exist.</returns>
|
||||||
Task<ServerBanDef?> GetServerBanAsync(int id);
|
Task<BanDef?> GetBanAsync(int id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up an user's most recent received un-pardoned ban.
|
/// Looks up an user's most recent received un-pardoned ban.
|
||||||
@@ -79,11 +79,12 @@ namespace Content.Server.Database
|
|||||||
/// <param name="hwId">The legacy HWID of the user.</param>
|
/// <param name="hwId">The legacy HWID of the user.</param>
|
||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
/// <param name="modernHWIds">The modern HWIDs 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(
|
Task<BanDef?> GetBanAsync(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds);
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
|
BanType type = BanType.Server);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up an user's ban history.
|
/// Looks up an user's ban history.
|
||||||
@@ -95,17 +96,18 @@ namespace Content.Server.Database
|
|||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
||||||
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
|
/// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
|
||||||
/// <returns>The user's ban history.</returns>
|
/// <returns>The user's ban history.</returns>
|
||||||
Task<List<ServerBanDef>> GetServerBansAsync(
|
Task<List<BanDef>> GetBansAsync(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned=true);
|
bool includeUnbanned=true,
|
||||||
|
BanType type = BanType.Server);
|
||||||
|
|
||||||
Task AddServerBanAsync(ServerBanDef serverBan);
|
Task<BanDef> AddBanAsync(BanDef ban);
|
||||||
Task AddServerUnbanAsync(ServerUnbanDef serverBan);
|
Task AddUnbanAsync(UnbanDef ban);
|
||||||
|
|
||||||
public Task EditServerBan(
|
public Task EditBan(
|
||||||
int id,
|
int id,
|
||||||
string reason,
|
string reason,
|
||||||
NoteSeverity severity,
|
NoteSeverity severity,
|
||||||
@@ -131,45 +133,6 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Role Bans
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a role ban by id.
|
|
||||||
/// This will return a pardoned role ban as well.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The role ban id to look for.</param>
|
|
||||||
/// <returns>The role ban with the given id or null if none exist.</returns>
|
|
||||||
Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up an user's role ban history.
|
|
||||||
/// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
|
|
||||||
/// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">The IP address of the user.</param>
|
|
||||||
/// <param name="userId">The NetUserId of the user.</param>
|
|
||||||
/// <param name="hwId">The Hardware Id of the user.</param>
|
|
||||||
/// <param name="modernHWIds">The modern HWIDs of the user.</param>
|
|
||||||
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
|
|
||||||
/// <returns>The user's role ban history.</returns>
|
|
||||||
Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
bool includeUnbanned = true);
|
|
||||||
|
|
||||||
Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan);
|
|
||||||
Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan);
|
|
||||||
|
|
||||||
public Task EditServerRoleBan(
|
|
||||||
int id,
|
|
||||||
string reason,
|
|
||||||
NoteSeverity severity,
|
|
||||||
DateTimeOffset? expiration,
|
|
||||||
Guid editedBy,
|
|
||||||
DateTimeOffset editedAt);
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Playtime
|
#region Playtime
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -209,7 +172,7 @@ namespace Content.Server.Database
|
|||||||
ConnectionDenyReason? denied,
|
ConnectionDenyReason? denied,
|
||||||
int serverId);
|
int serverId);
|
||||||
|
|
||||||
Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans);
|
Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -301,8 +264,7 @@ namespace Content.Server.Database
|
|||||||
Task<AdminNoteRecord?> GetAdminNote(int id);
|
Task<AdminNoteRecord?> GetAdminNote(int id);
|
||||||
Task<AdminWatchlistRecord?> GetAdminWatchlist(int id);
|
Task<AdminWatchlistRecord?> GetAdminWatchlist(int id);
|
||||||
Task<AdminMessageRecord?> GetAdminMessage(int id);
|
Task<AdminMessageRecord?> GetAdminMessage(int id);
|
||||||
Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id);
|
Task<BanNoteRecord?> GetBanAsNoteAsync(int id);
|
||||||
Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id);
|
|
||||||
Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
|
Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
|
||||||
Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player);
|
Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player);
|
||||||
Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
|
Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
|
||||||
@@ -313,8 +275,7 @@ namespace Content.Server.Database
|
|||||||
Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
||||||
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mark an admin message as being seen by the target player.
|
/// Mark an admin message as being seen by the target player.
|
||||||
@@ -522,49 +483,51 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.GetAssignedUserIdAsync(name));
|
return RunDbCommand(() => _db.GetAssignedUserIdAsync(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ServerBanDef?> GetServerBanAsync(int id)
|
public Task<BanDef?> GetBanAsync(int id)
|
||||||
{
|
{
|
||||||
DbReadOpsMetric.Inc();
|
DbReadOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.GetServerBanAsync(id));
|
return RunDbCommand(() => _db.GetBanAsync(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ServerBanDef?> GetServerBanAsync(
|
public Task<BanDef?> GetBanAsync(
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
|
||||||
{
|
|
||||||
DbReadOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.GetServerBanAsync(address, userId, hwId, modernHWIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<ServerBanDef>> GetServerBansAsync(
|
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned=true)
|
BanType type = BanType.Server)
|
||||||
{
|
{
|
||||||
DbReadOpsMetric.Inc();
|
DbReadOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
return RunDbCommand(() => _db.GetBanAsync(address, userId, hwId, modernHWIds, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task AddServerBanAsync(ServerBanDef serverBan)
|
public Task<List<BanDef>> GetBansAsync(
|
||||||
|
IPAddress? address,
|
||||||
|
NetUserId? userId,
|
||||||
|
ImmutableArray<byte>? hwId,
|
||||||
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
|
bool includeUnbanned=true,
|
||||||
|
BanType type = BanType.Server)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbReadOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.AddServerBanAsync(serverBan));
|
return RunDbCommand(() => _db.GetBansAsync(address, userId, hwId, modernHWIds, includeUnbanned, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
|
public Task<BanDef> AddBanAsync(BanDef ban)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban));
|
return RunDbCommand(() => _db.AddBanAsync(ban));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
public Task AddUnbanAsync(UnbanDef unban)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt));
|
return RunDbCommand(() => _db.AddUnbanAsync(unban));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task EditBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.EditBan(id, reason, severity, expiration, editedBy, editedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags)
|
public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags)
|
||||||
@@ -579,43 +542,6 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.GetBanExemption(userId, cancel));
|
return RunDbCommand(() => _db.GetBanExemption(userId, cancel));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Role Ban
|
|
||||||
public Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
|
|
||||||
{
|
|
||||||
DbReadOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.GetServerRoleBanAsync(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
bool includeUnbanned = true)
|
|
||||||
{
|
|
||||||
DbReadOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.GetServerRoleBansAsync(address, userId, hwId, modernHWIds, includeUnbanned));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
|
|
||||||
{
|
|
||||||
DbWriteOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.AddServerRoleBanAsync(serverRoleBan));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
|
|
||||||
{
|
|
||||||
DbWriteOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
|
|
||||||
{
|
|
||||||
DbWriteOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt));
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Playtime
|
#region Playtime
|
||||||
|
|
||||||
public Task<List<PlayTime>> GetPlayTimes(Guid player, CancellationToken cancel)
|
public Task<List<PlayTime>> GetPlayTimes(Guid player, CancellationToken cancel)
|
||||||
@@ -667,7 +593,7 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
|
return RunDbCommand(() => _db.AddConnectionLogAsync(userId, userName, address, hwId, trust, denied, serverId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task AddServerBanHitsAsync(int connection, IEnumerable<ServerBanDef> bans)
|
public Task AddServerBanHitsAsync(int connection, IEnumerable<BanDef> bans)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.AddServerBanHitsAsync(connection, bans));
|
return RunDbCommand(() => _db.AddServerBanHitsAsync(connection, bans));
|
||||||
@@ -928,16 +854,10 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.GetAdminMessage(id));
|
return RunDbCommand(() => _db.GetAdminMessage(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
public Task<BanNoteRecord?> GetBanAsNoteAsync(int id)
|
||||||
{
|
{
|
||||||
DbReadOpsMetric.Inc();
|
DbReadOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id));
|
return RunDbCommand(() => _db.GetBanAsNoteAsync(id));
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
|
|
||||||
{
|
|
||||||
DbReadOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
||||||
@@ -999,16 +919,10 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
|
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
public Task HideBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt));
|
return RunDbCommand(() => _db.HideBanFromNotes(id, deletedBy, deletedAt));
|
||||||
}
|
|
||||||
|
|
||||||
public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
|
|
||||||
{
|
|
||||||
DbWriteOpsMetric.Inc();
|
|
||||||
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task MarkMessageAsSeen(int id, bool dismissedToo)
|
public Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||||
|
|||||||
@@ -62,24 +62,26 @@ namespace Content.Server.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Ban
|
#region Ban
|
||||||
public override async Task<ServerBanDef?> GetServerBanAsync(int id)
|
public override async Task<BanDef?> GetBanAsync(int id)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
var query = db.PgDbContext.Ban
|
var query = db.PgDbContext.Ban
|
||||||
.Include(p => p.Unban)
|
.ApplyIncludes(GetBanDefIncludes())
|
||||||
.Where(p => p.Id == id);
|
.Where(p => p.Id == id)
|
||||||
|
.AsSplitQuery();
|
||||||
|
|
||||||
var ban = await query.SingleOrDefaultAsync();
|
var ban = await query.SingleOrDefaultAsync();
|
||||||
|
|
||||||
return ConvertBan(ban);
|
return ConvertBan(ban);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
public override async Task<BanDef?> GetBanAsync(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
|
BanType type)
|
||||||
{
|
{
|
||||||
if (address == null && userId == null && hwId == null)
|
if (address == null && userId == null && hwId == null)
|
||||||
{
|
{
|
||||||
@@ -90,7 +92,7 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
var exempt = await GetBanExemptionCore(db, userId);
|
var exempt = await GetBanExemptionCore(db, userId);
|
||||||
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
|
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
|
||||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer)
|
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned: false, exempt, newPlayer, type)
|
||||||
.OrderByDescending(b => b.BanTime);
|
.OrderByDescending(b => b.BanTime);
|
||||||
|
|
||||||
var ban = await query.FirstOrDefaultAsync();
|
var ban = await query.FirstOrDefaultAsync();
|
||||||
@@ -98,11 +100,12 @@ namespace Content.Server.Database
|
|||||||
return ConvertBan(ban);
|
return ConvertBan(ban);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address,
|
public override async Task<List<BanDef>> GetBansAsync(IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned)
|
bool includeUnbanned,
|
||||||
|
BanType type)
|
||||||
{
|
{
|
||||||
if (address == null && userId == null && hwId == null)
|
if (address == null && userId == null && hwId == null)
|
||||||
{
|
{
|
||||||
@@ -111,12 +114,11 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
var exempt = await GetBanExemptionCore(db, userId);
|
var exempt = type == BanType.Role ? null : await GetBanExemptionCore(db, userId);
|
||||||
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
|
var newPlayer = !await db.PgDbContext.Player.AnyAsync(p => p.UserId == userId);
|
||||||
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer);
|
var query = MakeBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned, exempt, newPlayer, type);
|
||||||
|
|
||||||
var queryBans = await query.ToArrayAsync();
|
var queryBans = await query.ToArrayAsync();
|
||||||
var bans = new List<ServerBanDef>(queryBans.Length);
|
var bans = new List<BanDef>(queryBans.Length);
|
||||||
|
|
||||||
foreach (var ban in queryBans)
|
foreach (var ban in queryBans)
|
||||||
{
|
{
|
||||||
@@ -131,7 +133,8 @@ namespace Content.Server.Database
|
|||||||
return bans;
|
return bans;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IQueryable<ServerBan> MakeBanLookupQuery(
|
// This has to return IDs instead of direct objects because otherwise all the includes are too complicated.
|
||||||
|
private static IQueryable<Ban> MakeBanLookupQuery(
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
@@ -139,34 +142,55 @@ namespace Content.Server.Database
|
|||||||
DbGuardImpl db,
|
DbGuardImpl db,
|
||||||
bool includeUnbanned,
|
bool includeUnbanned,
|
||||||
ServerBanExemptFlags? exemptFlags,
|
ServerBanExemptFlags? exemptFlags,
|
||||||
bool newPlayer)
|
bool newPlayer,
|
||||||
|
BanType type)
|
||||||
{
|
{
|
||||||
DebugTools.Assert(!(address == null && userId == null && hwId == null));
|
DebugTools.Assert(!(address == null && userId == null && hwId == null));
|
||||||
|
|
||||||
var query = MakeBanLookupQualityShared<ServerBan, ServerUnban>(
|
var selectorQueries = new List<IQueryable<IBanSelector>>();
|
||||||
userId,
|
|
||||||
hwId,
|
|
||||||
modernHWIds,
|
|
||||||
db.PgDbContext.Ban);
|
|
||||||
|
|
||||||
if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP))
|
if (userId is { } uid)
|
||||||
|
selectorQueries.Add(db.DbContext.BanPlayer.Where(b => b.UserId == uid.UserId));
|
||||||
|
|
||||||
|
if (hwId != null && hwId.Value.Length > 0)
|
||||||
{
|
{
|
||||||
var newQ = db.PgDbContext.Ban
|
selectorQueries.Add(db.DbContext.BanHwid.Where(bh =>
|
||||||
.Include(p => p.Unban)
|
bh.HWId!.Type == HwidType.Legacy && bh.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray())
|
||||||
.Where(b => b.Address != null
|
));
|
||||||
&& EF.Functions.ContainsOrEqual(b.Address.Value, address)
|
}
|
||||||
&& !(b.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) && !newPlayer));
|
|
||||||
|
|
||||||
query = query == null ? newQ : query.Union(newQ);
|
if (modernHWIds != null)
|
||||||
|
{
|
||||||
|
foreach (var modernHwid in modernHWIds)
|
||||||
|
{
|
||||||
|
selectorQueries.Add(db.DbContext.BanHwid
|
||||||
|
.Where(b => b.HWId!.Type == HwidType.Modern
|
||||||
|
&& b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address != null && !exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None)
|
||||||
|
.HasFlag(ServerBanExemptFlags.IP))
|
||||||
|
{
|
||||||
|
selectorQueries.Add(db.PgDbContext.BanAddress
|
||||||
|
.Where(ba => EF.Functions.ContainsOrEqual(ba.Address, address)
|
||||||
|
&& !(ba.Ban!.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) &&
|
||||||
|
!newPlayer)));
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugTools.Assert(
|
DebugTools.Assert(
|
||||||
query != null,
|
selectorQueries.Count > 0,
|
||||||
"At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
|
"At least one filter item (IP/UserID/HWID) must have been given to make query not null.");
|
||||||
|
|
||||||
|
var selectorQuery = selectorQueries
|
||||||
|
.Select(q => q.Select(sel => sel.BanId))
|
||||||
|
.Aggregate((selectors, queryable) => selectors.Union(queryable));
|
||||||
|
|
||||||
|
var banQuery = db.DbContext.Ban.Where(b => selectorQuery.Contains(b.Id));
|
||||||
|
|
||||||
if (!includeUnbanned)
|
if (!includeUnbanned)
|
||||||
{
|
{
|
||||||
query = query.Where(p =>
|
banQuery = banQuery.Where(p =>
|
||||||
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
|
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,68 +199,23 @@ namespace Content.Server.Database
|
|||||||
if (exempt != ServerBanExemptFlags.None)
|
if (exempt != ServerBanExemptFlags.None)
|
||||||
exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange
|
exempt |= ServerBanExemptFlags.BlacklistedRange; // Any kind of exemption should bypass BlacklistedRange
|
||||||
|
|
||||||
query = query.Where(b => (b.ExemptFlags & exempt) == 0);
|
banQuery = banQuery.Where(b => (b.ExemptFlags & exempt) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.Distinct();
|
return banQuery
|
||||||
|
.Where(b => b.Type == type)
|
||||||
|
.ApplyIncludes(GetBanDefIncludes(type))
|
||||||
|
.AsSplitQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IQueryable<TBan>? MakeBanLookupQualityShared<TBan, TUnban>(
|
[return: NotNullIfNotNull(nameof(ban))]
|
||||||
NetUserId? userId,
|
private static BanDef? ConvertBan(Ban? ban)
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
DbSet<TBan> set)
|
|
||||||
where TBan : class, IBanCommon<TUnban>
|
|
||||||
where TUnban : class, IUnbanCommon
|
|
||||||
{
|
|
||||||
IQueryable<TBan>? query = null;
|
|
||||||
|
|
||||||
if (userId is { } uid)
|
|
||||||
{
|
|
||||||
var newQ = set
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(b => b.PlayerUserId == uid.UserId);
|
|
||||||
|
|
||||||
query = query == null ? newQ : query.Union(newQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hwId != null && hwId.Value.Length > 0)
|
|
||||||
{
|
|
||||||
var newQ = set
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(b => b.HWId!.Type == HwidType.Legacy && b.HWId!.Hwid.SequenceEqual(hwId.Value.ToArray()));
|
|
||||||
|
|
||||||
query = query == null ? newQ : query.Union(newQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modernHWIds != null)
|
|
||||||
{
|
|
||||||
foreach (var modernHwid in modernHWIds)
|
|
||||||
{
|
|
||||||
var newQ = set
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(b => b.HWId!.Type == HwidType.Modern && b.HWId!.Hwid.SequenceEqual(modernHwid.ToArray()));
|
|
||||||
|
|
||||||
query = query == null ? newQ : query.Union(newQ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ServerBanDef? ConvertBan(ServerBan? ban)
|
|
||||||
{
|
{
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetUserId? uid = null;
|
|
||||||
if (ban.PlayerUserId is {} guid)
|
|
||||||
{
|
|
||||||
uid = new NetUserId(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
NetUserId? aUid = null;
|
||||||
if (ban.BanningAdmin is {} aGuid)
|
if (ban.BanningAdmin is {} aGuid)
|
||||||
{
|
{
|
||||||
@@ -245,23 +224,31 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
var unbanDef = ConvertUnban(ban.Unban);
|
var unbanDef = ConvertUnban(ban.Unban);
|
||||||
|
|
||||||
return new ServerBanDef(
|
ImmutableArray<BanRoleDef>? roles = null;
|
||||||
|
if (ban.Type == BanType.Role)
|
||||||
|
{
|
||||||
|
roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BanDef(
|
||||||
ban.Id,
|
ban.Id,
|
||||||
uid,
|
ban.Type,
|
||||||
ban.Address.ToTuple(),
|
[..ban.Players!.Select(bp => new NetUserId(bp.UserId))],
|
||||||
ban.HWId,
|
[..ban.Addresses!.Select(ba => ba.Address.ToTuple())],
|
||||||
|
[..ban.Hwids!.Select(bh => bh.HWId)],
|
||||||
ban.BanTime,
|
ban.BanTime,
|
||||||
ban.ExpirationTime,
|
ban.ExpirationTime,
|
||||||
ban.RoundId,
|
[..ban.Rounds!.Select(r => r.RoundId)],
|
||||||
ban.PlaytimeAtNote,
|
ban.PlaytimeAtNote,
|
||||||
ban.Reason,
|
ban.Reason,
|
||||||
ban.Severity,
|
ban.Severity,
|
||||||
aUid,
|
aUid,
|
||||||
unbanDef,
|
unbanDef,
|
||||||
ban.ExemptFlags);
|
ban.ExemptFlags,
|
||||||
|
roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServerUnbanDef? ConvertUnban(ServerUnban? unban)
|
private static UnbanDef? ConvertUnban(Unban? unban)
|
||||||
{
|
{
|
||||||
if (unban == null)
|
if (unban == null)
|
||||||
{
|
{
|
||||||
@@ -274,224 +261,54 @@ namespace Content.Server.Database
|
|||||||
aUid = new NetUserId(aGuid);
|
aUid = new NetUserId(aGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerUnbanDef(
|
return new UnbanDef(
|
||||||
unban.Id,
|
unban.Id,
|
||||||
aUid,
|
aUid,
|
||||||
unban.UnbanTime);
|
unban.UnbanTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task AddServerBanAsync(ServerBanDef serverBan)
|
public override async Task<BanDef> AddBanAsync(BanDef ban)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
db.PgDbContext.Ban.Add(new ServerBan
|
var banEntity = new Ban
|
||||||
{
|
{
|
||||||
Address = serverBan.Address.ToNpgsqlInet(),
|
Type = ban.Type,
|
||||||
HWId = serverBan.HWId,
|
Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })],
|
||||||
Reason = serverBan.Reason,
|
Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })],
|
||||||
Severity = serverBan.Severity,
|
Reason = ban.Reason,
|
||||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
Severity = ban.Severity,
|
||||||
BanTime = serverBan.BanTime.UtcDateTime,
|
BanningAdmin = ban.BanningAdmin?.UserId,
|
||||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
BanTime = ban.BanTime.UtcDateTime,
|
||||||
RoundId = serverBan.RoundId,
|
ExpirationTime = ban.ExpirationTime?.UtcDateTime,
|
||||||
PlaytimeAtNote = serverBan.PlaytimeAtNote,
|
Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })],
|
||||||
PlayerUserId = serverBan.UserId?.UserId,
|
PlaytimeAtNote = ban.PlaytimeAtNote,
|
||||||
ExemptFlags = serverBan.ExemptFlags
|
Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })],
|
||||||
});
|
ExemptFlags = ban.ExemptFlags,
|
||||||
|
Roles = ban.Roles == null
|
||||||
await db.PgDbContext.SaveChangesAsync();
|
? []
|
||||||
}
|
: ban.Roles.Value.Select(brd => new BanRole
|
||||||
|
{
|
||||||
public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
|
RoleType = brd.RoleType,
|
||||||
{
|
RoleId = brd.RoleId
|
||||||
await using var db = await GetDbImpl();
|
})
|
||||||
|
.ToList(),
|
||||||
db.PgDbContext.Unban.Add(new ServerUnban
|
|
||||||
{
|
|
||||||
BanId = serverUnban.BanId,
|
|
||||||
UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
|
|
||||||
UnbanTime = serverUnban.UnbanTime.UtcDateTime
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.PgDbContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Role Ban
|
|
||||||
public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
var query = db.PgDbContext.RoleBan
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(p => p.Id == id);
|
|
||||||
|
|
||||||
var ban = await query.SingleOrDefaultAsync();
|
|
||||||
|
|
||||||
return ConvertRoleBan(ban);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
bool includeUnbanned)
|
|
||||||
{
|
|
||||||
if (address == null && userId == null && hwId == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Address, userId, and hwId cannot all be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
var query = MakeRoleBanLookupQuery(address, userId, hwId, modernHWIds, db, includeUnbanned)
|
|
||||||
.OrderByDescending(b => b.BanTime);
|
|
||||||
|
|
||||||
return await QueryRoleBans(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<List<ServerRoleBanDef>> QueryRoleBans(IQueryable<ServerRoleBan> query)
|
|
||||||
{
|
|
||||||
var queryRoleBans = await query.ToArrayAsync();
|
|
||||||
var bans = new List<ServerRoleBanDef>(queryRoleBans.Length);
|
|
||||||
|
|
||||||
foreach (var ban in queryRoleBans)
|
|
||||||
{
|
|
||||||
var banDef = ConvertRoleBan(ban);
|
|
||||||
|
|
||||||
if (banDef != null)
|
|
||||||
{
|
|
||||||
bans.Add(banDef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bans;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IQueryable<ServerRoleBan> MakeRoleBanLookupQuery(
|
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
DbGuardImpl db,
|
|
||||||
bool includeUnbanned)
|
|
||||||
{
|
|
||||||
var query = MakeBanLookupQualityShared<ServerRoleBan, ServerRoleUnban>(
|
|
||||||
userId,
|
|
||||||
hwId,
|
|
||||||
modernHWIds,
|
|
||||||
db.PgDbContext.RoleBan);
|
|
||||||
|
|
||||||
if (address != null)
|
|
||||||
{
|
|
||||||
var newQ = db.PgDbContext.RoleBan
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address));
|
|
||||||
|
|
||||||
query = query == null ? newQ : query.Union(newQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!includeUnbanned)
|
|
||||||
{
|
|
||||||
query = query?.Where(p =>
|
|
||||||
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query!.Distinct();
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(ban))]
|
|
||||||
private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
|
|
||||||
{
|
|
||||||
if (ban == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? uid = null;
|
|
||||||
if (ban.PlayerUserId is {} guid)
|
|
||||||
{
|
|
||||||
uid = new NetUserId(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
|
||||||
if (ban.BanningAdmin is {} aGuid)
|
|
||||||
{
|
|
||||||
aUid = new NetUserId(aGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
var unbanDef = ConvertRoleUnban(ban.Unban);
|
|
||||||
|
|
||||||
return new ServerRoleBanDef(
|
|
||||||
ban.Id,
|
|
||||||
uid,
|
|
||||||
ban.Address.ToTuple(),
|
|
||||||
ban.HWId,
|
|
||||||
ban.BanTime,
|
|
||||||
ban.ExpirationTime,
|
|
||||||
ban.RoundId,
|
|
||||||
ban.PlaytimeAtNote,
|
|
||||||
ban.Reason,
|
|
||||||
ban.Severity,
|
|
||||||
aUid,
|
|
||||||
unbanDef,
|
|
||||||
ban.RoleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
|
|
||||||
{
|
|
||||||
if (unban == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
|
||||||
if (unban.UnbanningAdmin is {} aGuid)
|
|
||||||
{
|
|
||||||
aUid = new NetUserId(aGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ServerRoleUnbanDef(
|
|
||||||
unban.Id,
|
|
||||||
aUid,
|
|
||||||
unban.UnbanTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
var ban = new ServerRoleBan
|
|
||||||
{
|
|
||||||
Address = serverRoleBan.Address.ToNpgsqlInet(),
|
|
||||||
HWId = serverRoleBan.HWId,
|
|
||||||
Reason = serverRoleBan.Reason,
|
|
||||||
Severity = serverRoleBan.Severity,
|
|
||||||
BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
|
|
||||||
BanTime = serverRoleBan.BanTime.UtcDateTime,
|
|
||||||
ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime,
|
|
||||||
RoundId = serverRoleBan.RoundId,
|
|
||||||
PlaytimeAtNote = serverRoleBan.PlaytimeAtNote,
|
|
||||||
PlayerUserId = serverRoleBan.UserId?.UserId,
|
|
||||||
RoleId = serverRoleBan.Role,
|
|
||||||
};
|
};
|
||||||
db.PgDbContext.RoleBan.Add(ban);
|
db.PgDbContext.Ban.Add(banEntity);
|
||||||
|
|
||||||
await db.PgDbContext.SaveChangesAsync();
|
await db.PgDbContext.SaveChangesAsync();
|
||||||
return ConvertRoleBan(ban);
|
return ConvertBan(banEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
|
public override async Task AddUnbanAsync(UnbanDef unban)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
db.PgDbContext.RoleUnban.Add(new ServerRoleUnban
|
db.PgDbContext.Unban.Add(new Unban
|
||||||
{
|
{
|
||||||
BanId = serverRoleUnban.BanId,
|
BanId = unban.BanId,
|
||||||
UnbanningAdmin = serverRoleUnban.UnbanningAdmin?.UserId,
|
UnbanningAdmin = unban.UnbanningAdmin?.UserId,
|
||||||
UnbanTime = serverRoleUnban.UnbanTime.UtcDateTime
|
UnbanTime = unban.UnbanTime.UtcDateTime
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.PgDbContext.SaveChangesAsync();
|
await db.PgDbContext.SaveChangesAsync();
|
||||||
|
|||||||
@@ -70,48 +70,52 @@ namespace Content.Server.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Ban
|
#region Ban
|
||||||
public override async Task<ServerBanDef?> GetServerBanAsync(int id)
|
public override async Task<BanDef?> GetBanAsync(int id)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
var ban = await db.SqliteDbContext.Ban
|
var ban = await db.SqliteDbContext.Ban
|
||||||
.Include(p => p.Unban)
|
.ApplyIncludes(GetBanDefIncludes())
|
||||||
.Where(p => p.Id == id)
|
.Where(p => p.Id == id)
|
||||||
|
.AsSplitQuery()
|
||||||
.SingleOrDefaultAsync();
|
.SingleOrDefaultAsync();
|
||||||
|
|
||||||
return ConvertBan(ban);
|
return ConvertBan(ban);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<ServerBanDef?> GetServerBanAsync(
|
public override async Task<BanDef?> GetBanAsync(
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false)).FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(
|
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned)
|
BanType type)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned)).ToList();
|
return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned: false, type)).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<ServerBanDef>> GetServerBanQueryAsync(
|
public override async Task<List<BanDef>> GetBansAsync(
|
||||||
|
IPAddress? address,
|
||||||
|
NetUserId? userId,
|
||||||
|
ImmutableArray<byte>? hwId,
|
||||||
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
|
bool includeUnbanned,
|
||||||
|
BanType type)
|
||||||
|
{
|
||||||
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
|
return (await GetBanQueryAsync(db, address, userId, hwId, modernHWIds, includeUnbanned, type)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<BanDef>> GetBanQueryAsync(
|
||||||
DbGuardImpl db,
|
DbGuardImpl db,
|
||||||
IPAddress? address,
|
IPAddress? address,
|
||||||
NetUserId? userId,
|
NetUserId? userId,
|
||||||
ImmutableArray<byte>? hwId,
|
ImmutableArray<byte>? hwId,
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
||||||
bool includeUnbanned)
|
bool includeUnbanned,
|
||||||
|
BanType type)
|
||||||
{
|
{
|
||||||
var exempt = await GetBanExemptionCore(db, userId);
|
var exempt = await GetBanExemptionCore(db, userId);
|
||||||
|
|
||||||
@@ -119,7 +123,7 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
// SQLite can't do the net masking stuff we need to match IP address ranges.
|
// SQLite can't do the net masking stuff we need to match IP address ranges.
|
||||||
// So just pull down the whole list into memory.
|
// So just pull down the whole list into memory.
|
||||||
var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);
|
var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt, type);
|
||||||
|
|
||||||
var playerInfo = new BanMatcher.PlayerInfo
|
var playerInfo = new BanMatcher.PlayerInfo
|
||||||
{
|
{
|
||||||
@@ -136,12 +140,12 @@ namespace Content.Server.Database
|
|||||||
.Where(b => BanMatcher.BanMatches(b!, playerInfo))!;
|
.Where(b => BanMatcher.BanMatches(b!, playerInfo))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<ServerBan>> GetAllBans(
|
private static async Task<List<Ban>> GetAllBans(SqliteServerDbContext db,
|
||||||
SqliteServerDbContext db,
|
|
||||||
bool includeUnbanned,
|
bool includeUnbanned,
|
||||||
ServerBanExemptFlags? exemptFlags)
|
ServerBanExemptFlags? exemptFlags,
|
||||||
|
BanType type)
|
||||||
{
|
{
|
||||||
IQueryable<ServerBan> query = db.Ban.Include(p => p.Unban);
|
var query = db.Ban.Where(b => b.Type == type).ApplyIncludes(GetBanDefIncludes(type));
|
||||||
if (!includeUnbanned)
|
if (!includeUnbanned)
|
||||||
{
|
{
|
||||||
query = query.Where(p =>
|
query = query.Where(p =>
|
||||||
@@ -157,244 +161,65 @@ namespace Content.Server.Database
|
|||||||
query = query.Where(b => (b.ExemptFlags & exempt) == 0);
|
query = query.Where(b => (b.ExemptFlags & exempt) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await query.ToListAsync();
|
return await query.AsSplitQuery().ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task AddServerBanAsync(ServerBanDef serverBan)
|
public override async Task<BanDef> AddBanAsync(BanDef ban)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
db.SqliteDbContext.Ban.Add(new ServerBan
|
var banEntity = new Ban
|
||||||
{
|
{
|
||||||
Address = serverBan.Address.ToNpgsqlInet(),
|
Type = ban.Type,
|
||||||
Reason = serverBan.Reason,
|
Addresses = [..ban.Addresses.Select(ba => new BanAddress { Address = ba.ToNpgsqlInet() })],
|
||||||
Severity = serverBan.Severity,
|
Hwids = [..ban.HWIds.Select(bh => new BanHwid { HWId = bh })],
|
||||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
Reason = ban.Reason,
|
||||||
HWId = serverBan.HWId,
|
Severity = ban.Severity,
|
||||||
BanTime = serverBan.BanTime.UtcDateTime,
|
BanningAdmin = ban.BanningAdmin?.UserId,
|
||||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
BanTime = ban.BanTime.UtcDateTime,
|
||||||
RoundId = serverBan.RoundId,
|
ExpirationTime = ban.ExpirationTime?.UtcDateTime,
|
||||||
PlaytimeAtNote = serverBan.PlaytimeAtNote,
|
Rounds = [..ban.RoundIds.Select(bri => new BanRound { RoundId = bri })],
|
||||||
PlayerUserId = serverBan.UserId?.UserId,
|
PlaytimeAtNote = ban.PlaytimeAtNote,
|
||||||
ExemptFlags = serverBan.ExemptFlags
|
Players = [..ban.UserIds.Select(bp => new BanPlayer { UserId = bp.UserId })],
|
||||||
});
|
ExemptFlags = ban.ExemptFlags,
|
||||||
|
Roles = ban.Roles == null
|
||||||
await db.SqliteDbContext.SaveChangesAsync();
|
? []
|
||||||
}
|
: ban.Roles.Value.Select(brd => new BanRole
|
||||||
|
|
||||||
public override async Task AddServerUnbanAsync(ServerUnbanDef serverUnban)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
db.SqliteDbContext.Unban.Add(new ServerUnban
|
|
||||||
{
|
|
||||||
BanId = serverUnban.BanId,
|
|
||||||
UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
|
|
||||||
UnbanTime = serverUnban.UnbanTime.UtcDateTime
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.SqliteDbContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Role Ban
|
|
||||||
public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
var ban = await db.SqliteDbContext.RoleBan
|
|
||||||
.Include(p => p.Unban)
|
|
||||||
.Where(p => p.Id == id)
|
|
||||||
.SingleOrDefaultAsync();
|
|
||||||
|
|
||||||
return ConvertRoleBan(ban);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
|
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds,
|
|
||||||
bool includeUnbanned)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
// SQLite can't do the net masking stuff we need to match IP address ranges.
|
|
||||||
// So just pull down the whole list into memory.
|
|
||||||
var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned);
|
|
||||||
|
|
||||||
return queryBans
|
|
||||||
.Where(b => RoleBanMatches(b, address, userId, hwId, modernHWIds))
|
|
||||||
.Select(ConvertRoleBan)
|
|
||||||
.ToList()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<List<ServerRoleBan>> GetAllRoleBans(
|
|
||||||
SqliteServerDbContext db,
|
|
||||||
bool includeUnbanned)
|
|
||||||
{
|
|
||||||
IQueryable<ServerRoleBan> query = db.RoleBan.Include(p => p.Unban);
|
|
||||||
if (!includeUnbanned)
|
|
||||||
{
|
|
||||||
query = query.Where(p =>
|
|
||||||
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await query.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool RoleBanMatches(
|
|
||||||
ServerRoleBan ban,
|
|
||||||
IPAddress? address,
|
|
||||||
NetUserId? userId,
|
|
||||||
ImmutableArray<byte>? hwId,
|
|
||||||
ImmutableArray<ImmutableArray<byte>>? modernHWIds)
|
|
||||||
{
|
|
||||||
if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userId is { } id && ban.PlayerUserId == id.UserId)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (ban.HWId?.Type)
|
|
||||||
{
|
|
||||||
case HwidType.Legacy:
|
|
||||||
if (hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HwidType.Modern:
|
|
||||||
if (modernHWIds != null)
|
|
||||||
{
|
|
||||||
foreach (var modernHWId in modernHWIds)
|
|
||||||
{
|
{
|
||||||
if (modernHWId.AsSpan().SequenceEqual(ban.HWId.Hwid))
|
RoleType = brd.RoleType,
|
||||||
return true;
|
RoleId = brd.RoleId
|
||||||
}
|
})
|
||||||
}
|
.ToList(),
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverBan)
|
|
||||||
{
|
|
||||||
await using var db = await GetDbImpl();
|
|
||||||
|
|
||||||
var ban = new ServerRoleBan
|
|
||||||
{
|
|
||||||
Address = serverBan.Address.ToNpgsqlInet(),
|
|
||||||
Reason = serverBan.Reason,
|
|
||||||
Severity = serverBan.Severity,
|
|
||||||
BanningAdmin = serverBan.BanningAdmin?.UserId,
|
|
||||||
HWId = serverBan.HWId,
|
|
||||||
BanTime = serverBan.BanTime.UtcDateTime,
|
|
||||||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
|
||||||
RoundId = serverBan.RoundId,
|
|
||||||
PlaytimeAtNote = serverBan.PlaytimeAtNote,
|
|
||||||
PlayerUserId = serverBan.UserId?.UserId,
|
|
||||||
RoleId = serverBan.Role,
|
|
||||||
};
|
};
|
||||||
db.SqliteDbContext.RoleBan.Add(ban);
|
db.SqliteDbContext.Ban.Add(banEntity);
|
||||||
|
|
||||||
await db.SqliteDbContext.SaveChangesAsync();
|
await db.SqliteDbContext.SaveChangesAsync();
|
||||||
return ConvertRoleBan(ban);
|
return ConvertBan(banEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnban)
|
public override async Task AddUnbanAsync(UnbanDef unban)
|
||||||
{
|
{
|
||||||
await using var db = await GetDbImpl();
|
await using var db = await GetDbImpl();
|
||||||
|
|
||||||
db.SqliteDbContext.RoleUnban.Add(new ServerRoleUnban
|
db.SqliteDbContext.Unban.Add(new Unban
|
||||||
{
|
{
|
||||||
BanId = serverUnban.BanId,
|
BanId = unban.BanId,
|
||||||
UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
|
UnbanningAdmin = unban.UnbanningAdmin?.UserId,
|
||||||
UnbanTime = serverUnban.UnbanTime.UtcDateTime
|
UnbanTime = unban.UnbanTime.UtcDateTime
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.SqliteDbContext.SaveChangesAsync();
|
await db.SqliteDbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(ban))]
|
|
||||||
private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
|
|
||||||
{
|
|
||||||
if (ban == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? uid = null;
|
|
||||||
if (ban.PlayerUserId is { } guid)
|
|
||||||
{
|
|
||||||
uid = new NetUserId(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
|
||||||
if (ban.BanningAdmin is { } aGuid)
|
|
||||||
{
|
|
||||||
aUid = new NetUserId(aGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
var unban = ConvertRoleUnban(ban.Unban);
|
|
||||||
|
|
||||||
return new ServerRoleBanDef(
|
|
||||||
ban.Id,
|
|
||||||
uid,
|
|
||||||
ban.Address.ToTuple(),
|
|
||||||
ban.HWId,
|
|
||||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
|
||||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
|
||||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
|
||||||
ban.RoundId,
|
|
||||||
ban.PlaytimeAtNote,
|
|
||||||
ban.Reason,
|
|
||||||
ban.Severity,
|
|
||||||
aUid,
|
|
||||||
unban,
|
|
||||||
ban.RoleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
|
|
||||||
{
|
|
||||||
if (unban == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
|
||||||
if (unban.UnbanningAdmin is { } aGuid)
|
|
||||||
{
|
|
||||||
aUid = new NetUserId(aGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ServerRoleUnbanDef(
|
|
||||||
unban.Id,
|
|
||||||
aUid,
|
|
||||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
|
||||||
DateTime.SpecifyKind(unban.UnbanTime, DateTimeKind.Utc));
|
|
||||||
}
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(ban))]
|
[return: NotNullIfNotNull(nameof(ban))]
|
||||||
private static ServerBanDef? ConvertBan(ServerBan? ban)
|
private static BanDef? ConvertBan(Ban? ban)
|
||||||
{
|
{
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetUserId? uid = null;
|
|
||||||
if (ban.PlayerUserId is { } guid)
|
|
||||||
{
|
|
||||||
uid = new NetUserId(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetUserId? aUid = null;
|
NetUserId? aUid = null;
|
||||||
if (ban.BanningAdmin is { } aGuid)
|
if (ban.BanningAdmin is { } aGuid)
|
||||||
{
|
{
|
||||||
@@ -403,23 +228,32 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
var unban = ConvertUnban(ban.Unban);
|
var unban = ConvertUnban(ban.Unban);
|
||||||
|
|
||||||
return new ServerBanDef(
|
ImmutableArray<BanRoleDef>? roles = null;
|
||||||
|
if (ban.Type == BanType.Role)
|
||||||
|
{
|
||||||
|
roles = [..ban.Roles!.Select(br => new BanRoleDef(br.RoleType, br.RoleId))];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BanDef(
|
||||||
ban.Id,
|
ban.Id,
|
||||||
uid,
|
ban.Type,
|
||||||
ban.Address.ToTuple(),
|
[..ban.Players!.Select(bp => new NetUserId(bp.UserId))],
|
||||||
ban.HWId,
|
[..ban.Addresses!.Select(ba => ba.Address.ToTuple())],
|
||||||
|
[..ban.Hwids!.Select(bh => bh.HWId)],
|
||||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||||
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
|
||||||
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
|
||||||
ban.RoundId,
|
[..ban.Rounds!.Select(r => r.RoundId)],
|
||||||
ban.PlaytimeAtNote,
|
ban.PlaytimeAtNote,
|
||||||
ban.Reason,
|
ban.Reason,
|
||||||
ban.Severity,
|
ban.Severity,
|
||||||
aUid,
|
aUid,
|
||||||
unban);
|
unban,
|
||||||
|
ban.ExemptFlags,
|
||||||
|
roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServerUnbanDef? ConvertUnban(ServerUnban? unban)
|
private static UnbanDef? ConvertUnban(Unban? unban)
|
||||||
{
|
{
|
||||||
if (unban == null)
|
if (unban == null)
|
||||||
{
|
{
|
||||||
@@ -432,7 +266,7 @@ namespace Content.Server.Database
|
|||||||
aUid = new NetUserId(aGuid);
|
aUid = new NetUserId(aGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerUnbanDef(
|
return new UnbanDef(
|
||||||
unban.Id,
|
unban.Id,
|
||||||
aUid,
|
aUid,
|
||||||
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Robust.Shared.Network;
|
|
||||||
|
|
||||||
namespace Content.Server.Database;
|
|
||||||
|
|
||||||
public sealed class ServerRoleBanDef
|
|
||||||
{
|
|
||||||
public int? Id { get; }
|
|
||||||
public NetUserId? UserId { get; }
|
|
||||||
public (IPAddress address, int cidrMask)? Address { get; }
|
|
||||||
public ImmutableTypedHwid? HWId { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset BanTime { get; }
|
|
||||||
public DateTimeOffset? ExpirationTime { get; }
|
|
||||||
public int? RoundId { get; }
|
|
||||||
public TimeSpan PlaytimeAtNote { get; }
|
|
||||||
public string Reason { get; }
|
|
||||||
public NoteSeverity Severity { get; set; }
|
|
||||||
public NetUserId? BanningAdmin { get; }
|
|
||||||
public ServerRoleUnbanDef? Unban { get; }
|
|
||||||
public string Role { get; }
|
|
||||||
|
|
||||||
public ServerRoleBanDef(
|
|
||||||
int? id,
|
|
||||||
NetUserId? userId,
|
|
||||||
(IPAddress, int)? address,
|
|
||||||
ImmutableTypedHwid? hwId,
|
|
||||||
DateTimeOffset banTime,
|
|
||||||
DateTimeOffset? expirationTime,
|
|
||||||
int? roundId,
|
|
||||||
TimeSpan playtimeAtNote,
|
|
||||||
string reason,
|
|
||||||
NoteSeverity severity,
|
|
||||||
NetUserId? banningAdmin,
|
|
||||||
ServerRoleUnbanDef? unban,
|
|
||||||
string role)
|
|
||||||
{
|
|
||||||
if (userId == null && address == null && hwId == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
|
|
||||||
{
|
|
||||||
// Fix IPv6-mapped IPv4 addresses
|
|
||||||
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
|
|
||||||
address = (addr.Item1.MapToIPv4(), addr.Item2 - 96);
|
|
||||||
}
|
|
||||||
|
|
||||||
Id = id;
|
|
||||||
UserId = userId;
|
|
||||||
Address = address;
|
|
||||||
HWId = hwId;
|
|
||||||
BanTime = banTime;
|
|
||||||
ExpirationTime = expirationTime;
|
|
||||||
RoundId = roundId;
|
|
||||||
PlaytimeAtNote = playtimeAtNote;
|
|
||||||
Reason = reason;
|
|
||||||
Severity = severity;
|
|
||||||
BanningAdmin = banningAdmin;
|
|
||||||
Unban = unban;
|
|
||||||
Role = role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Robust.Shared.Network;
|
|
||||||
|
|
||||||
namespace Content.Server.Database;
|
|
||||||
|
|
||||||
public sealed class ServerRoleUnbanDef
|
|
||||||
{
|
|
||||||
public int BanId { get; }
|
|
||||||
|
|
||||||
public NetUserId? UnbanningAdmin { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset UnbanTime { get; }
|
|
||||||
|
|
||||||
public ServerRoleUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
|
|
||||||
{
|
|
||||||
BanId = banId;
|
|
||||||
UnbanningAdmin = unbanningAdmin;
|
|
||||||
UnbanTime = unbanTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Content.Server.Database
|
namespace Content.Server.Database
|
||||||
{
|
{
|
||||||
public sealed class ServerUnbanDef
|
public sealed class UnbanDef
|
||||||
{
|
{
|
||||||
public int BanId { get; }
|
public int BanId { get; }
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
public DateTimeOffset UnbanTime { get; }
|
public DateTimeOffset UnbanTime { get; }
|
||||||
|
|
||||||
public ServerUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
|
public UnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
|
||||||
{
|
{
|
||||||
BanId = banId;
|
BanId = banId;
|
||||||
UnbanningAdmin = unbanningAdmin;
|
UnbanningAdmin = unbanningAdmin;
|
||||||
@@ -11,22 +11,14 @@ namespace Content.Server.IP
|
|||||||
{
|
{
|
||||||
// Npgsql used to map inet types as a tuple like this.
|
// Npgsql used to map inet types as a tuple like this.
|
||||||
// I'm upgrading the dependencies and I don't wanna rewrite a bunch of DB code, so a few helpers it shall be.
|
// I'm upgrading the dependencies and I don't wanna rewrite a bunch of DB code, so a few helpers it shall be.
|
||||||
[return: NotNullIfNotNull(nameof(tuple))]
|
public static NpgsqlInet ToNpgsqlInet(this (IPAddress, int) tuple)
|
||||||
public static NpgsqlInet? ToNpgsqlInet(this (IPAddress, int)? tuple)
|
|
||||||
{
|
{
|
||||||
if (tuple == null)
|
return new NpgsqlInet(tuple.Item1, (byte)tuple.Item2);
|
||||||
return null;
|
|
||||||
|
|
||||||
return new NpgsqlInet(tuple.Value.Item1, (byte) tuple.Value.Item2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(inet))]
|
public static (IPAddress, int) ToTuple(this NpgsqlInet inet)
|
||||||
public static (IPAddress, int)? ToTuple(this NpgsqlInet? inet)
|
|
||||||
{
|
{
|
||||||
if (inet == null)
|
return (inet.Address, inet.Netmask);
|
||||||
return null;
|
|
||||||
|
|
||||||
return (inet.Value.Address, inet.Value.Netmask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Taken from https://stackoverflow.com/a/56461160/4678631
|
// Taken from https://stackoverflow.com/a/56461160/4678631
|
||||||
|
|||||||
@@ -371,14 +371,7 @@ namespace Content.Server.Voting.Managers
|
|||||||
}
|
}
|
||||||
var targetUid = located.UserId;
|
var targetUid = located.UserId;
|
||||||
var targetHWid = located.LastHWId;
|
var targetHWid = located.LastHWId;
|
||||||
(IPAddress, int)? targetIP = null;
|
var targetIP = located.LastAddress;
|
||||||
|
|
||||||
if (located.LastAddress is not null)
|
|
||||||
{
|
|
||||||
targetIP = located.LastAddress.AddressFamily is AddressFamily.InterNetwork
|
|
||||||
? (located.LastAddress, 32) // People with ipv4 addresses get a /32 address so we ban that
|
|
||||||
: (located.LastAddress, 64); // This can only be an ipv6 address. People with ipv6 address should get /64 addresses so we ban that.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession))
|
if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession))
|
||||||
{
|
{
|
||||||
@@ -544,7 +537,15 @@ namespace Content.Server.Voting.Managers
|
|||||||
|
|
||||||
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
||||||
|
|
||||||
_bans.CreateServerBan(targetUid, target, null, targetIP, targetHWid, minutes, severity, Loc.GetString("votekick-ban-reason", ("reason", reason)));
|
var banInfo = new CreateServerBanInfo(Loc.GetString("votekick-ban-reason", ("reason", reason)));
|
||||||
|
banInfo.AddUser(targetUid, target);
|
||||||
|
banInfo.AddHWId(targetHWid);
|
||||||
|
banInfo.AddAddress(targetIP);
|
||||||
|
banInfo.WithSeverity(severity);
|
||||||
|
if (minutes > 0)
|
||||||
|
banInfo.WithMinutes(minutes);
|
||||||
|
|
||||||
|
_bans.CreateServerBan(banInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
33
Content.Shared.Database/Bans.cs
Normal file
33
Content.Shared.Database/Bans.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Content.Shared.Database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Types of bans that can be stored in the database.
|
||||||
|
/// </summary>
|
||||||
|
public enum BanType : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ban from the entire server. If a player matches the ban info, they will be refused connection.
|
||||||
|
/// </summary>
|
||||||
|
Server,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A ban from playing one or more roles.
|
||||||
|
/// </summary>
|
||||||
|
Role,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single role for a database role ban.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="RoleType">The type of role being banned, e.g. <c>Job</c>.</param>
|
||||||
|
/// <param name="RoleId">
|
||||||
|
/// The ID of the role being banned. This is likely a prototype ID based on <paramref name="RoleType"/>.
|
||||||
|
/// </param>
|
||||||
|
[Serializable]
|
||||||
|
public record struct BanRoleDef(string RoleType, string RoleId)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{RoleType}:{RoleId}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ namespace Content.Shared.Administration.BanList;
|
|||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class BanListEuiState : EuiStateBase
|
public sealed class BanListEuiState : EuiStateBase
|
||||||
{
|
{
|
||||||
public BanListEuiState(string banListPlayerName, List<SharedServerBan> bans, List<SharedServerRoleBan> roleBans)
|
public BanListEuiState(string banListPlayerName, List<SharedBan> bans, List<SharedBan> roleBans)
|
||||||
{
|
{
|
||||||
BanListPlayerName = banListPlayerName;
|
BanListPlayerName = banListPlayerName;
|
||||||
Bans = bans;
|
Bans = bans;
|
||||||
@@ -14,6 +14,6 @@ public sealed class BanListEuiState : EuiStateBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string BanListPlayerName { get; }
|
public string BanListPlayerName { get; }
|
||||||
public List<SharedServerBan> Bans { get; }
|
public List<SharedBan> Bans { get; }
|
||||||
public List<SharedServerRoleBan> RoleBans { get; }
|
public List<SharedBan> RoleBans { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
20
Content.Shared/Administration/BanList/SharedBan.cs
Normal file
20
Content.Shared/Administration/BanList/SharedBan.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Administration.BanList;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public record SharedBan(
|
||||||
|
int? Id,
|
||||||
|
BanType Type,
|
||||||
|
ImmutableArray<NetUserId> UserIds,
|
||||||
|
ImmutableArray<(string address, int cidrMask)> Addresses,
|
||||||
|
ImmutableArray<string> HWIds,
|
||||||
|
DateTime BanTime,
|
||||||
|
DateTime? ExpirationTime,
|
||||||
|
string Reason,
|
||||||
|
string? BanningAdminName,
|
||||||
|
SharedUnban? Unban,
|
||||||
|
ImmutableArray<BanRoleDef>? Roles);
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Robust.Shared.Network;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Administration.BanList;
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public record SharedServerBan(
|
|
||||||
int? Id,
|
|
||||||
NetUserId? UserId,
|
|
||||||
(string address, int cidrMask)? Address,
|
|
||||||
string? HWId,
|
|
||||||
DateTime BanTime,
|
|
||||||
DateTime? ExpirationTime,
|
|
||||||
string Reason,
|
|
||||||
string? BanningAdminName,
|
|
||||||
SharedServerUnban? Unban
|
|
||||||
);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Robust.Shared.Network;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Administration.BanList;
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed record SharedServerRoleBan(
|
|
||||||
int? Id,
|
|
||||||
NetUserId? UserId,
|
|
||||||
(string address, int cidrMask)? Address,
|
|
||||||
string? HWId,
|
|
||||||
DateTime BanTime,
|
|
||||||
DateTime? ExpirationTime,
|
|
||||||
string Reason,
|
|
||||||
string? BanningAdminName,
|
|
||||||
SharedServerUnban? Unban,
|
|
||||||
string Role
|
|
||||||
) : SharedServerBan(Id, UserId, Address, HWId, BanTime, ExpirationTime, Reason, BanningAdminName, Unban);
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace Content.Shared.Administration.BanList;
|
namespace Content.Shared.Administration.BanList;
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed record SharedServerUnban(
|
public sealed record SharedUnban(
|
||||||
string? UnbanningAdmin,
|
string? UnbanningAdmin,
|
||||||
DateTime UnbanTime
|
DateTime UnbanTime
|
||||||
);
|
);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
@@ -7,8 +8,8 @@ namespace Content.Shared.Administration.Notes;
|
|||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed record SharedAdminNote(
|
public sealed record SharedAdminNote(
|
||||||
int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note.
|
int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note.
|
||||||
NetUserId Player, // Notes player
|
ImmutableArray<NetUserId> Players, // Notes player
|
||||||
int? Round, // Which round was it added in?
|
ImmutableArray<int> Rounds, // Which round was it added in?
|
||||||
string? ServerName, // Which server was this added on?
|
string? ServerName, // Which server was this added on?
|
||||||
TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note
|
TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note
|
||||||
NoteType NoteType, // Type of note
|
NoteType NoteType, // Type of note
|
||||||
@@ -20,7 +21,7 @@ public sealed record SharedAdminNote(
|
|||||||
DateTime CreatedAt, // When was it created?
|
DateTime CreatedAt, // When was it created?
|
||||||
DateTime? LastEditedAt, // When was it last edited?
|
DateTime? LastEditedAt, // When was it last edited?
|
||||||
DateTime? ExpiryTime, // Does it expire?
|
DateTime? ExpiryTime, // Does it expire?
|
||||||
string[]? BannedRoles, // Only valid for role bans. List of banned roles
|
ImmutableArray<BanRoleDef>? BannedRoles, // Only valid for role bans. List of banned roles
|
||||||
DateTime? UnbannedTime, // Only valid for bans. Set if unbanned
|
DateTime? UnbannedTime, // Only valid for bans. Set if unbanned
|
||||||
string? UnbannedByName, // Only valid for bans. Set if unbanned
|
string? UnbannedByName, // Only valid for bans. Set if unbanned
|
||||||
bool? Seen // Only valid for messages, otherwise should be null. Has the user seen this message?
|
bool? Seen // Only valid for messages, otherwise should be null. Has the user seen this message?
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using Content.Shared.Roles;
|
||||||
using Lidgren.Network;
|
using Lidgren.Network;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Players;
|
namespace Content.Shared.Players;
|
||||||
@@ -11,8 +13,8 @@ public sealed class MsgRoleBans : NetMessage
|
|||||||
{
|
{
|
||||||
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;
|
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;
|
||||||
|
|
||||||
public List<string> JobBans = new();
|
public List<ProtoId<JobPrototype>> JobBans = new();
|
||||||
public List<string> AntagBans = new();
|
public List<ProtoId<AntagPrototype>> AntagBans = new();
|
||||||
|
|
||||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ cmd-roleban-severity-parse = ${severity} is not a valid severity\n{$help}.
|
|||||||
cmd-roleban-arg-count = Invalid amount of arguments.
|
cmd-roleban-arg-count = Invalid amount of arguments.
|
||||||
cmd-roleban-job-parse = Job {$job} does not exist.
|
cmd-roleban-job-parse = Job {$job} does not exist.
|
||||||
cmd-roleban-name-parse = Unable to find a player with that name.
|
cmd-roleban-name-parse = Unable to find a player with that name.
|
||||||
cmd-roleban-existing = {$target} already has a role ban for {$role}.
|
|
||||||
cmd-roleban-success = Role banned {$target} from {$role} with reason {$reason} {$length}.
|
cmd-roleban-success = Role banned {$target} from {$role} with reason {$reason} {$length}.
|
||||||
|
|
||||||
cmd-roleban-inf = permanently
|
cmd-roleban-inf = permanently
|
||||||
|
|||||||
@@ -131,6 +131,7 @@
|
|||||||
- fuck
|
- fuck
|
||||||
- replay_recording_start
|
- replay_recording_start
|
||||||
- replay_recording_stop
|
- replay_recording_stop
|
||||||
|
- transfer_test
|
||||||
|
|
||||||
- Flags: QUERY
|
- Flags: QUERY
|
||||||
Commands:
|
Commands:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import os
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
LATEST_DB_MIGRATION = "20250314222016_ConstructionFavorites"
|
LATEST_DB_MIGRATION = "20260120200503_BanRefactor"
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -42,9 +42,8 @@ def main():
|
|||||||
dump_player(cur, user_id, arg_output)
|
dump_player(cur, user_id, arg_output)
|
||||||
dump_preference(cur, user_id, arg_output)
|
dump_preference(cur, user_id, arg_output)
|
||||||
dump_role_whitelists(cur, user_id, arg_output)
|
dump_role_whitelists(cur, user_id, arg_output)
|
||||||
dump_server_ban(cur, user_id, arg_output)
|
dump_ban(cur, user_id, arg_output)
|
||||||
dump_server_ban_exemption(cur, user_id, arg_output)
|
dump_server_ban_exemption(cur, user_id, arg_output)
|
||||||
dump_server_role_ban(cur, user_id, arg_output)
|
|
||||||
dump_uploaded_resource_log(cur, user_id, arg_output)
|
dump_uploaded_resource_log(cur, user_id, arg_output)
|
||||||
dump_whitelist(cur, user_id, arg_output)
|
dump_whitelist(cur, user_id, arg_output)
|
||||||
|
|
||||||
@@ -301,7 +300,7 @@ FROM (
|
|||||||
f.write(json_data)
|
f.write(json_data)
|
||||||
|
|
||||||
|
|
||||||
def dump_server_ban(cur: "psycopg2.cursor", user_id: str, outdir: str):
|
def dump_ban(cur: "psycopg2.cursor", user_id: str, outdir: str):
|
||||||
print("Dumping server_ban...")
|
print("Dumping server_ban...")
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
@@ -311,19 +310,39 @@ FROM (
|
|||||||
SELECT
|
SELECT
|
||||||
*,
|
*,
|
||||||
(SELECT to_jsonb(unban_sq) - 'ban_id' FROM (
|
(SELECT to_jsonb(unban_sq) - 'ban_id' FROM (
|
||||||
SELECT * FROM server_unban WHERE server_unban.ban_id = server_ban.server_ban_id
|
SELECT * FROM unban WHERE unban.ban_id = ban.ban_id
|
||||||
) unban_sq)
|
) unban_sq)
|
||||||
as unban
|
as unban,
|
||||||
|
(SELECT COALESCE(json_agg(to_jsonb(ban_player_subq) - 'ban_id'), '[]') FROM (
|
||||||
|
SELECT * FROM ban_player WHERE ban_player.ban_id = ban.ban_id
|
||||||
|
) ban_player_subq)
|
||||||
|
as ban_player,
|
||||||
|
(SELECT COALESCE(json_agg(to_jsonb(ban_address_subq) - 'ban_id'), '[]') FROM (
|
||||||
|
SELECT * FROM ban_address WHERE ban_address.ban_id = ban.ban_id
|
||||||
|
) ban_address_subq)
|
||||||
|
as ban_address,
|
||||||
|
(SELECT COALESCE(json_agg(to_jsonb(ban_role_subq) - 'ban_id'), '[]') FROM (
|
||||||
|
SELECT * FROM ban_role WHERE ban_role.ban_id = ban.ban_id
|
||||||
|
) ban_role_subq)
|
||||||
|
as ban_role,
|
||||||
|
(SELECT COALESCE(json_agg(to_jsonb(ban_hwid_subq) - 'ban_id'), '[]') FROM (
|
||||||
|
SELECT * FROM ban_hwid WHERE ban_hwid.ban_id = ban.ban_id
|
||||||
|
) ban_hwid_subq)
|
||||||
|
as ban_hwid,
|
||||||
|
(SELECT COALESCE(json_agg(to_jsonb(ban_round_subq) - 'ban_id'), '[]') FROM (
|
||||||
|
SELECT * FROM ban_round WHERE ban_round.ban_id = ban.ban_id
|
||||||
|
) ban_round_subq)
|
||||||
|
as ban_round
|
||||||
FROM
|
FROM
|
||||||
server_ban
|
ban
|
||||||
WHERE
|
WHERE
|
||||||
player_user_id = %s
|
ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s)
|
||||||
) as data
|
) as data
|
||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
|
|
||||||
json_data = cur.fetchall()[0][0]
|
json_data = cur.fetchall()[0][0]
|
||||||
|
|
||||||
with open(os.path.join(outdir, "server_ban.json"), "w", encoding="utf-8") as f:
|
with open(os.path.join(outdir, "ban.json"), "w", encoding="utf-8") as f:
|
||||||
f.write(json_data)
|
f.write(json_data)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import os
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
LATEST_DB_MIGRATION = "20250314222016_ConstructionFavorites"
|
LATEST_DB_MIGRATION = "20260120200503_BanRefactor"
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -38,9 +38,8 @@ def main():
|
|||||||
clear_play_time(cur, user_id)
|
clear_play_time(cur, user_id)
|
||||||
clear_player(cur, user_id)
|
clear_player(cur, user_id)
|
||||||
clear_preference(cur, user_id)
|
clear_preference(cur, user_id)
|
||||||
clear_server_ban(cur, user_id)
|
clear_ban(cur, user_id)
|
||||||
clear_server_ban_exemption(cur, user_id)
|
clear_server_ban_exemption(cur, user_id)
|
||||||
clear_server_role_ban(cur, user_id)
|
|
||||||
clear_uploaded_resource_log(cur, user_id)
|
clear_uploaded_resource_log(cur, user_id)
|
||||||
clear_whitelist(cur, user_id)
|
clear_whitelist(cur, user_id)
|
||||||
clear_blacklist(cur, user_id)
|
clear_blacklist(cur, user_id)
|
||||||
@@ -144,14 +143,14 @@ WHERE
|
|||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
|
|
||||||
|
|
||||||
def clear_server_ban(cur: "psycopg2.cursor", user_id: str):
|
def clear_ban(cur: "psycopg2.cursor", user_id: str):
|
||||||
print("Clearing server_ban...")
|
print("Clearing ban...")
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
server_ban
|
ban
|
||||||
WHERE
|
WHERE
|
||||||
player_user_id = %s
|
ban_id IN (SELECT bp.ban_id FROM ban_player bp WHERE bp.user_id = %s)
|
||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
|
|
||||||
|
|
||||||
@@ -166,17 +165,6 @@ WHERE
|
|||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
|
|
||||||
|
|
||||||
def clear_server_role_ban(cur: "psycopg2.cursor", user_id: str):
|
|
||||||
print("Clearing server_role_ban...")
|
|
||||||
|
|
||||||
cur.execute("""
|
|
||||||
DELETE FROM
|
|
||||||
server_role_ban
|
|
||||||
WHERE
|
|
||||||
player_user_id = %s
|
|
||||||
""", (user_id,))
|
|
||||||
|
|
||||||
|
|
||||||
def clear_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str):
|
def clear_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str):
|
||||||
print("Clearing uploaded_resource_log...")
|
print("Clearing uploaded_resource_log...")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user