Automated whitelists (#23985)
* Beginnings of making the breadmemes jobs easier * stuff * stuff pt. 2 * Stuff pt.3 * Stuff I forgot last time * Basic whitelist Only people that are added to the whitelist with the addwhitelist command will be able to join. I call this the "legacy" whitelist * Remove always deny condition in favor of just breaking if playtime check fails * Change default whitelist Default whitelist is now the "legacy" whitelist. * localization * Admin check * minor spelling change * Fix build * Whitelist message * Fix vars not being datafield and spelling mistakes * Minor spelling mistake * Change config for salamander * Reviews and stuff * Add summaries * Fix whitelists * Forgot to add a datafield * Fixing stuff I guess * Reuse admin remarks to reduce load when connecting. * Update log messages to be verbose instead of debug * Reviews * whoops * Explain a bit more how whitelist checking works * Apply CE's review * Append Membership to Blacklist and Whitelist conditions * Fix review comments * Uncapitalize playerConnectionWhitelist, add to ignored client prototypes * Make note count field work * Fix cvar for thingy --------- Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -108,6 +108,7 @@ namespace Content.Client.Entry
|
|||||||
_prototypeManager.RegisterIgnore("lobbyBackground");
|
_prototypeManager.RegisterIgnore("lobbyBackground");
|
||||||
_prototypeManager.RegisterIgnore("gamePreset");
|
_prototypeManager.RegisterIgnore("gamePreset");
|
||||||
_prototypeManager.RegisterIgnore("noiseChannel");
|
_prototypeManager.RegisterIgnore("noiseChannel");
|
||||||
|
_prototypeManager.RegisterIgnore("playerConnectionWhitelist");
|
||||||
_prototypeManager.RegisterIgnore("spaceBiome");
|
_prototypeManager.RegisterIgnore("spaceBiome");
|
||||||
_prototypeManager.RegisterIgnore("worldgenConfig");
|
_prototypeManager.RegisterIgnore("worldgenConfig");
|
||||||
_prototypeManager.RegisterIgnore("gameRule");
|
_prototypeManager.RegisterIgnore("gameRule");
|
||||||
|
|||||||
1769
Content.Server.Database/Migrations/Postgres/20240112194620_Blacklist.Designer.cs
generated
Normal file
1769
Content.Server.Database/Migrations/Postgres/20240112194620_Blacklist.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Blacklist : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "blacklist",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
user_id = table.Column<Guid>(type: "uuid", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_blacklist", x => x.user_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "blacklist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -512,6 +512,20 @@ 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.Blacklist",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("user_id");
|
||||||
|
|
||||||
|
b.HasKey("UserId")
|
||||||
|
.HasName("PK_blacklist");
|
||||||
|
|
||||||
|
b.ToTable("blacklist", (string) null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|||||||
1701
Content.Server.Database/Migrations/Sqlite/20240112194612_Blacklist.Designer.cs
generated
Normal file
1701
Content.Server.Database/Migrations/Sqlite/20240112194612_Blacklist.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Blacklist : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "blacklist",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
user_id = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_blacklist", x => x.user_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "blacklist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -483,6 +483,19 @@ 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.Blacklist",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("user_id");
|
||||||
|
|
||||||
|
b.HasKey("UserId")
|
||||||
|
.HasName("PK_blacklist");
|
||||||
|
|
||||||
|
b.ToTable("blacklist", (string) null);
|
||||||
|
});
|
||||||
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
modelBuilder.Entity("Content.Server.Database.BanTemplate", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ namespace Content.Server.Database
|
|||||||
public DbSet<AdminLog> AdminLog { get; set; } = null!;
|
public DbSet<AdminLog> AdminLog { get; set; } = null!;
|
||||||
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<ServerBan> Ban { get; set; } = default!;
|
public DbSet<ServerBan> Ban { get; set; } = default!;
|
||||||
public DbSet<ServerUnban> Unban { get; set; } = default!;
|
public DbSet<ServerUnban> Unban { get; set; } = default!;
|
||||||
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
|
public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
|
||||||
@@ -551,6 +552,15 @@ namespace Content.Server.Database
|
|||||||
[Required, Key] public Guid UserId { get; set; }
|
[Required, Key] public Guid UserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of users who are on the "blacklist". This is a list that may be used by Whitelist implementations to deny access to certain users.
|
||||||
|
/// </summary>
|
||||||
|
[Table("blacklist")]
|
||||||
|
public class Blacklist
|
||||||
|
{
|
||||||
|
[Required, Key] public Guid UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Admin
|
public class Admin
|
||||||
{
|
{
|
||||||
[Key] public Guid UserId { get; set; }
|
[Key] public Guid UserId { get; set; }
|
||||||
|
|||||||
221
Content.Server/Connection/ConnectionManager.Whitelist.cs
Normal file
221
Content.Server/Connection/ConnectionManager.Whitelist.cs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Connection.Whitelist;
|
||||||
|
using Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles whitelist conditions for incoming connections.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConnectionManager
|
||||||
|
{
|
||||||
|
private PlayerConnectionWhitelistPrototype[]? _whitelists;
|
||||||
|
|
||||||
|
public void PostInit()
|
||||||
|
{
|
||||||
|
_cfg.OnValueChanged(CCVars.WhitelistPrototypeList, UpdateWhitelists, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWhitelists(string s)
|
||||||
|
{
|
||||||
|
var list = new List<PlayerConnectionWhitelistPrototype>();
|
||||||
|
foreach (var id in s.Split(','))
|
||||||
|
{
|
||||||
|
if (_prototypeManager.TryIndex(id, out PlayerConnectionWhitelistPrototype? prototype))
|
||||||
|
{
|
||||||
|
list.Add(prototype);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_sawmill.Fatal($"Whitelist prototype {id} does not exist. Denying all connections.");
|
||||||
|
_whitelists = null; // Invalidate the list, causes deny on all connections.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_whitelists = list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValid(PlayerConnectionWhitelistPrototype whitelist, int playerCount)
|
||||||
|
{
|
||||||
|
return playerCount >= whitelist.MinimumPlayers && playerCount <= whitelist.MaximumPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool isWhitelisted, string? denyMessage)> IsWhitelisted(PlayerConnectionWhitelistPrototype whitelist, NetUserData data, ISawmill sawmill)
|
||||||
|
{
|
||||||
|
var cacheRemarks = await _db.GetAllAdminRemarks(data.UserId);
|
||||||
|
var cachePlaytime = await _db.GetPlayTimes(data.UserId);
|
||||||
|
|
||||||
|
foreach (var condition in whitelist.Conditions)
|
||||||
|
{
|
||||||
|
bool matched;
|
||||||
|
string denyMessage;
|
||||||
|
switch (condition)
|
||||||
|
{
|
||||||
|
case ConditionAlwaysMatch:
|
||||||
|
matched = true;
|
||||||
|
denyMessage = Loc.GetString("whitelist-always-deny");
|
||||||
|
break;
|
||||||
|
case ConditionManualWhitelistMembership:
|
||||||
|
matched = await CheckConditionManualWhitelist(data);
|
||||||
|
denyMessage = Loc.GetString("whitelist-manual");
|
||||||
|
break;
|
||||||
|
case ConditionManualBlacklistMembership:
|
||||||
|
matched = await CheckConditionManualBlacklist(data);
|
||||||
|
denyMessage = Loc.GetString("whitelist-blacklisted");
|
||||||
|
break;
|
||||||
|
case ConditionNotesDateRange conditionNotes:
|
||||||
|
matched = CheckConditionNotesDateRange(conditionNotes, cacheRemarks);
|
||||||
|
denyMessage = Loc.GetString("whitelist-notes");
|
||||||
|
break;
|
||||||
|
case ConditionPlayerCount conditionPlayerCount:
|
||||||
|
matched = CheckConditionPlayerCount(conditionPlayerCount);
|
||||||
|
denyMessage = Loc.GetString("whitelist-player-count");
|
||||||
|
break;
|
||||||
|
case ConditionPlaytime conditionPlaytime:
|
||||||
|
matched = CheckConditionPlaytime(conditionPlaytime, cachePlaytime);
|
||||||
|
denyMessage = Loc.GetString("whitelist-playtime", ("minutes", conditionPlaytime.MinimumPlaytime));
|
||||||
|
break;
|
||||||
|
case ConditionNotesPlaytimeRange conditionNotesPlaytimeRange:
|
||||||
|
matched = CheckConditionNotesPlaytimeRange(conditionNotesPlaytimeRange, cacheRemarks, cachePlaytime);
|
||||||
|
denyMessage = Loc.GetString("whitelist-notes");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"Whitelist condition {condition.GetType().Name} not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
sawmill.Verbose($"User {data.UserName} whitelist condition {condition.GetType().Name} result: {matched}");
|
||||||
|
sawmill.Verbose($"Action: {condition.Action.ToString()}");
|
||||||
|
switch (condition.Action)
|
||||||
|
{
|
||||||
|
case ConditionAction.Allow:
|
||||||
|
if (matched)
|
||||||
|
{
|
||||||
|
sawmill.Verbose($"User {data.UserName} passed whitelist condition {condition.GetType().Name} and it's a breaking condition");
|
||||||
|
return (true, denyMessage);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ConditionAction.Deny:
|
||||||
|
if (matched)
|
||||||
|
{
|
||||||
|
sawmill.Verbose($"User {data.UserName} failed whitelist condition {condition.GetType().Name}");
|
||||||
|
return (false, denyMessage);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sawmill.Verbose($"User {data.UserName} failed whitelist condition {condition.GetType().Name} but it's not a breaking condition");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sawmill.Verbose($"User {data.UserName} passed all whitelist conditions");
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Condition Checking
|
||||||
|
|
||||||
|
private async Task<bool> CheckConditionManualWhitelist(NetUserData data)
|
||||||
|
{
|
||||||
|
return !(await _db.GetWhitelistStatusAsync(data.UserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CheckConditionManualBlacklist(NetUserData data)
|
||||||
|
{
|
||||||
|
return await _db.GetBlacklistStatusAsync(data.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckConditionNotesDateRange(ConditionNotesDateRange conditionNotes, List<IAdminRemarksRecord> remarks)
|
||||||
|
{
|
||||||
|
var range = DateTime.UtcNow.AddDays(-conditionNotes.Range);
|
||||||
|
|
||||||
|
return CheckRemarks(remarks,
|
||||||
|
conditionNotes.IncludeExpired,
|
||||||
|
conditionNotes.IncludeSecret,
|
||||||
|
conditionNotes.MinimumSeverity,
|
||||||
|
conditionNotes.MinimumNotes,
|
||||||
|
adminRemarksRecord => adminRemarksRecord.CreatedAt > range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckConditionPlayerCount(ConditionPlayerCount conditionPlayerCount)
|
||||||
|
{
|
||||||
|
var count = _plyMgr.PlayerCount;
|
||||||
|
return count >= conditionPlayerCount.MinimumPlayers && count <= conditionPlayerCount.MaximumPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckConditionPlaytime(ConditionPlaytime conditionPlaytime, List<PlayTime> playtime)
|
||||||
|
{
|
||||||
|
var tracker = playtime.Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
||||||
|
if (tracker is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracker.TimeSpent.TotalMinutes >= conditionPlaytime.MinimumPlaytime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckConditionNotesPlaytimeRange(
|
||||||
|
ConditionNotesPlaytimeRange conditionNotesPlaytimeRange,
|
||||||
|
List<IAdminRemarksRecord> remarks,
|
||||||
|
List<PlayTime> playtime)
|
||||||
|
{
|
||||||
|
var overallTracker = playtime.Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
|
||||||
|
if (overallTracker is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckRemarks(remarks,
|
||||||
|
conditionNotesPlaytimeRange.IncludeExpired,
|
||||||
|
conditionNotesPlaytimeRange.IncludeSecret,
|
||||||
|
conditionNotesPlaytimeRange.MinimumSeverity,
|
||||||
|
conditionNotesPlaytimeRange.MinimumNotes,
|
||||||
|
adminRemarksRecord => adminRemarksRecord.PlaytimeAtNote >= overallTracker.TimeSpent - TimeSpan.FromMinutes(conditionNotesPlaytimeRange.Range));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckRemarks(List<IAdminRemarksRecord> remarks, bool includeExpired, bool includeSecret, NoteSeverity minimumSeverity, int MinimumNotes, Func<IAdminRemarksRecord, bool> additionalCheck)
|
||||||
|
{
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var notes = remarks.Count(r => r is AdminNoteRecord note && note.Severity >= minimumSeverity && (includeSecret || !note.Secret) && (includeExpired || note.ExpirationTime == null || note.ExpirationTime > utcNow));
|
||||||
|
if (notes < MinimumNotes)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var adminRemarksRecord in remarks)
|
||||||
|
{
|
||||||
|
// If we're not including expired notes, skip them
|
||||||
|
if (!includeExpired && (adminRemarksRecord.ExpirationTime == null || adminRemarksRecord.ExpirationTime <= utcNow))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// In order to get the severity of the remark, we need to see if it's an AdminNoteRecord.
|
||||||
|
if (adminRemarksRecord is not AdminNoteRecord adminNoteRecord)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// We want to filter out secret notes if we're not including them.
|
||||||
|
if (!includeSecret && adminNoteRecord.Secret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// At this point, we need to remove the note if it's not within the severity range.
|
||||||
|
if (adminNoteRecord.Severity < minimumSeverity)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Perform the additional check specific to each method
|
||||||
|
if (!additionalCheck(adminRemarksRecord))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If we've made it this far, we have a match
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matches
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Connection.Whitelist;
|
||||||
|
using Content.Server.Connection.Whitelist.Conditions;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,6 +18,7 @@ using Robust.Server.Player;
|
|||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
@@ -26,6 +31,7 @@ namespace Content.Server.Connection
|
|||||||
public interface IConnectionManager
|
public interface IConnectionManager
|
||||||
{
|
{
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
void PostInit();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporarily allow a user to bypass regular connection requirements.
|
/// Temporarily allow a user to bypass regular connection requirements.
|
||||||
@@ -43,7 +49,7 @@ namespace Content.Server.Connection
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles various duties like guest username assignment, bans, connection logs, etc...
|
/// Handles various duties like guest username assignment, bans, connection logs, etc...
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ConnectionManager : IConnectionManager
|
public sealed partial class ConnectionManager : IConnectionManager
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||||
[Dependency] private readonly IPlayerManager _plyMgr = default!;
|
[Dependency] private readonly IPlayerManager _plyMgr = default!;
|
||||||
@@ -52,12 +58,14 @@ namespace Content.Server.Connection
|
|||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||||
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
|
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
|
||||||
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
|
||||||
|
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
@@ -268,20 +276,33 @@ namespace Content.Server.Connection
|
|||||||
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
|
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
|
// Checks for whitelist IF it's enabled AND the user isn't an admin. Admins are always allowed.
|
||||||
|
if (_cfg.GetCVar(CCVars.WhitelistEnabled) && adminData is null)
|
||||||
{
|
{
|
||||||
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
|
if (_whitelists is null)
|
||||||
var max = _cfg.GetCVar(CCVars.WhitelistMaxPlayers);
|
|
||||||
var playerCountValid = _plyMgr.PlayerCount >= min && _plyMgr.PlayerCount < max;
|
|
||||||
|
|
||||||
if (playerCountValid && await _db.GetWhitelistStatusAsync(userId) == false
|
|
||||||
&& adminData is null)
|
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString(_cfg.GetCVar(CCVars.WhitelistReason));
|
_sawmill.Error("Whitelist enabled but no whitelists loaded.");
|
||||||
// was the whitelist playercount changed?
|
// Misconfigured, deny everyone.
|
||||||
if (min > 0 || max < int.MaxValue)
|
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-misconfigured"), null);
|
||||||
msg += "\n" + Loc.GetString("whitelist-playercount-invalid", ("min", min), ("max", max));
|
}
|
||||||
return (ConnectionDenyReason.Whitelist, msg, null);
|
|
||||||
|
foreach (var whitelist in _whitelists)
|
||||||
|
{
|
||||||
|
if (!IsValid(whitelist, _plyMgr.PlayerCount))
|
||||||
|
{
|
||||||
|
// Not valid for current player count.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistStatus = await IsWhitelisted(whitelist, e.UserData, _sawmill);
|
||||||
|
if (!whitelistStatus.isWhitelisted)
|
||||||
|
{
|
||||||
|
// Not whitelisted.
|
||||||
|
return (ConnectionDenyReason.Whitelist, Loc.GetString("whitelist-fail-prefix", ("msg", whitelistStatus.denyMessage!)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelisted, don't check any more.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
117
Content.Server/Connection/Whitelist/BlacklistCommands.cs
Normal file
117
Content.Server/Connection/Whitelist/BlacklistCommands.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Ban)]
|
||||||
|
public sealed class AddBlacklistCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
|
||||||
|
public override string Command => "blacklistadd";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[0];
|
||||||
|
var data = await _playerLocator.LookupIdByNameAsync(name);
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-blacklistadd-not-found", ("username", args[0])));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var guid = data.UserId;
|
||||||
|
var isBlacklisted = await _db.GetBlacklistStatusAsync(guid);
|
||||||
|
if (isBlacklisted)
|
||||||
|
{
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-blacklistadd-existing", ("username", data.Username)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db.AddToBlacklistAsync(guid);
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-blacklistadd-added", ("username", data.Username)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHint(Loc.GetString("cmd-blacklistadd-arg-player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Ban)]
|
||||||
|
public sealed class RemoveBlacklistCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
|
||||||
|
public override string Command => "blacklistremove";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-need-minimum-one-argument"));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
|
||||||
|
shell.WriteLine(Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[0];
|
||||||
|
var data = await _playerLocator.LookupIdByNameAsync(name);
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-blacklistremove-not-found", ("username", args[0])));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = data.UserId;
|
||||||
|
var isBlacklisted = await _db.GetBlacklistStatusAsync(guid);
|
||||||
|
if (!isBlacklisted)
|
||||||
|
{
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-blacklistremove-existing", ("username", data.Username)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _db.RemoveFromBlacklistAsync(guid);
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-blacklistremove-removed", ("username", data.Username)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
return CompletionResult.FromHint(Loc.GetString("cmd-blacklistremove-arg-player"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that always matches
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionAlwaysMatch : WhitelistCondition
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player is in the manual blacklist.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionManualBlacklistMembership : WhitelistCondition
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player is in the manual whitelist.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionManualWhitelistMembership : WhitelistCondition
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player has notes within a certain date range.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionNotesDateRange : WhitelistCondition
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public bool IncludeExpired = false;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public NoteSeverity MinimumSeverity = NoteSeverity.Minor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum number of notes required.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int MinimumNotes = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Range in days to check for notes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int Range = int.MaxValue;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public bool IncludeSecret = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.Database;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player has notes within a certain playtime range.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionNotesPlaytimeRange : WhitelistCondition
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public bool IncludeExpired = false;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public NoteSeverity MinimumSeverity = NoteSeverity.Minor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum number of notes required.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int MinimumNotes = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The range in minutes to check for notes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int Range = int.MaxValue;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public bool IncludeSecret = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player count is within a certain range.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionPlayerCount : WhitelistCondition
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public int MinimumPlayers = 0;
|
||||||
|
[DataField]
|
||||||
|
public int MaximumPlayers = int.MaxValue;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Condition that matches if the player has played for a certain amount of time.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ConditionPlaytime : WhitelistCondition
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public int MinimumPlaytime = 0; // In minutes
|
||||||
|
}
|
||||||
41
Content.Server/Connection/Whitelist/WhitelistCondition.cs
Normal file
41
Content.Server/Connection/Whitelist/WhitelistCondition.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is used to determine if a player should be allowed to join the server.
|
||||||
|
/// It is used in <see cref="PlayerConnectionWhitelistPrototype"/>
|
||||||
|
/// </summary>
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
[MeansImplicitUse]
|
||||||
|
public abstract partial class WhitelistCondition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// What action should be taken if this condition is met?
|
||||||
|
/// Defaults to <see cref="ConditionAction.Next"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ConditionAction Action { get; set; } = ConditionAction.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines what action should be taken if a condition is met.
|
||||||
|
/// </summary>
|
||||||
|
public enum ConditionAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The player is allowed to join, and the next conditions will be skipped.
|
||||||
|
/// </summary>
|
||||||
|
Allow,
|
||||||
|
/// <summary>
|
||||||
|
/// The player is denied to join, and the next conditions will be skipped.
|
||||||
|
/// </summary>
|
||||||
|
Deny,
|
||||||
|
/// <summary>
|
||||||
|
/// The next condition should be checked.
|
||||||
|
/// </summary>
|
||||||
|
Next
|
||||||
|
}
|
||||||
42
Content.Server/Connection/Whitelist/WhitelistPrototype.cs
Normal file
42
Content.Server/Connection/Whitelist/WhitelistPrototype.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection.Whitelist;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by the <see cref="ConnectionManager"/> to determine if a player should be allowed to join the server.
|
||||||
|
/// Used in the whitelist.prototype_list CVar.
|
||||||
|
///
|
||||||
|
/// Whitelists are used to determine if a player is allowed to connect.
|
||||||
|
/// You define a PlayerConnectionWhitelist with a list of conditions.
|
||||||
|
/// Every condition has a type and a <see cref="ConditionAction"/> along with other parameters depending on the type.
|
||||||
|
/// Action must either be Allow, Deny or Next.
|
||||||
|
/// Allow means the player is instantly allowed to connect if the condition is met.
|
||||||
|
/// Deny means the player is instantly denied to connect if the condition is met.
|
||||||
|
/// Next means the next condition in the list is checked.
|
||||||
|
/// If the condition doesn't match, the next condition is checked.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("playerConnectionWhitelist")]
|
||||||
|
public sealed class PlayerConnectionWhitelistPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum number of players required for this whitelist to be active.
|
||||||
|
/// If there are less players than this, the whitelist will be ignored and the next one in the list will be used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int MinimumPlayers { get; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of players allowed for this whitelist to be active.
|
||||||
|
/// If there are more players than this, the whitelist will be ignored and the next one in the list will be used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int MaximumPlayers { get; } = int.MaxValue;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public WhitelistCondition[] Conditions { get; } = default!;
|
||||||
|
}
|
||||||
@@ -1066,6 +1066,29 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetBlacklistStatusAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
|
||||||
|
return await db.DbContext.Blacklist.AnyAsync(w => w.UserId == player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddToBlacklistAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
|
||||||
|
db.DbContext.Blacklist.Add(new Blacklist() { UserId = player });
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveFromBlacklistAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
var entry = await db.DbContext.Blacklist.SingleAsync(w => w.UserId == player);
|
||||||
|
db.DbContext.Blacklist.Remove(entry);
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Uploaded Resources Logs
|
#region Uploaded Resources Logs
|
||||||
|
|||||||
@@ -244,6 +244,16 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Blacklist
|
||||||
|
|
||||||
|
Task<bool> GetBlacklistStatusAsync(NetUserId player);
|
||||||
|
|
||||||
|
Task AddToBlacklistAsync(NetUserId player);
|
||||||
|
|
||||||
|
Task RemoveFromBlacklistAsync(NetUserId player);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Uploaded Resources Logs
|
#region Uploaded Resources Logs
|
||||||
|
|
||||||
Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data);
|
Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data);
|
||||||
@@ -740,6 +750,24 @@ namespace Content.Server.Database
|
|||||||
return RunDbCommand(() => _db.RemoveFromWhitelistAsync(player));
|
return RunDbCommand(() => _db.RemoveFromWhitelistAsync(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetBlacklistStatusAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
DbReadOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.GetBlacklistStatusAsync(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddToBlacklistAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.AddToBlacklistAsync(player));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveFromBlacklistAsync(NetUserId player)
|
||||||
|
{
|
||||||
|
DbWriteOpsMetric.Inc();
|
||||||
|
return RunDbCommand(() => _db.RemoveFromBlacklistAsync(player));
|
||||||
|
}
|
||||||
|
|
||||||
public Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data)
|
public Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data)
|
||||||
{
|
{
|
||||||
DbWriteOpsMetric.Inc();
|
DbWriteOpsMetric.Inc();
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ namespace Content.Server.Entry
|
|||||||
IoCManager.Resolve<IGameMapManager>().Initialize();
|
IoCManager.Resolve<IGameMapManager>().Initialize();
|
||||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||||
IoCManager.Resolve<IBanManager>().Initialize();
|
IoCManager.Resolve<IBanManager>().Initialize();
|
||||||
|
IoCManager.Resolve<IConnectionManager>().PostInit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1326,24 +1326,12 @@ namespace Content.Shared.CCVar
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<bool> WhitelistEnabled =
|
public static readonly CVarDef<bool> WhitelistEnabled =
|
||||||
CVarDef.Create("whitelist.enabled", false, CVar.SERVERONLY);
|
CVarDef.Create("whitelist.enabled", false, CVar.SERVERONLY);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The loc string to display as a disconnect reason when someone is not whitelisted.
|
/// Specifies the whitelist prototypes to be used by the server. This should be a comma-separated list of prototypes.
|
||||||
|
/// If a whitelists conditions to be active fail (for example player count), the next whitelist will be used instead. If no whitelist is valid, the player will be allowed to connect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<string> WhitelistReason =
|
public static readonly CVarDef<string> WhitelistPrototypeList =
|
||||||
CVarDef.Create("whitelist.reason", "whitelist-not-whitelisted", CVar.SERVERONLY);
|
CVarDef.Create("whitelist.prototype_list", "basicWhitelist", CVar.SERVERONLY);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the playercount is below this number, the whitelist will not apply.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly CVarDef<int> WhitelistMinPlayers =
|
|
||||||
CVarDef.Create("whitelist.min_players", 0, CVar.SERVERONLY);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the playercount is above this number, the whitelist will not apply.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly CVarDef<int> WhitelistMaxPlayers =
|
|
||||||
CVarDef.Create("whitelist.max_players", int.MaxValue, CVar.SERVERONLY);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* VOTE
|
* VOTE
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Configuration preset used on Wizard's Den Salamander
|
# Configuration preset used on Wizard's Den Salamander
|
||||||
|
|
||||||
[game]
|
[game]
|
||||||
desc = "Official English Space Station 14 servers. Medium roleplay ruleset. You must be whitelisted through Discord to play if there are more than 15 online players."
|
desc = "Official English Space Station 14 servers. Medium roleplay ruleset. you must be whitelisted by playing on other Wizard's Den servers if there are more than 15 online players."
|
||||||
hostname = "[EN] Wizard's Den Salamander [US West RP]"
|
hostname = "[EN] Wizard's Den Salamander [US West RP]"
|
||||||
soft_max_players = 130
|
soft_max_players = 130
|
||||||
|
|
||||||
@@ -10,8 +10,7 @@ rules_file = "MRPRuleset"
|
|||||||
|
|
||||||
[whitelist]
|
[whitelist]
|
||||||
enabled = true
|
enabled = true
|
||||||
reason = "whitelist-not-whitelisted-rp"
|
prototype_list = "salamanderMrpWhitelist"
|
||||||
min_players = 15
|
|
||||||
|
|
||||||
[shuttle]
|
[shuttle]
|
||||||
emergency_early_launch_allowed = true
|
emergency_early_launch_allowed = true
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
whitelist-not-whitelisted = You are not whitelisted.
|
cmd-whitelistadd-desc = Adds the player with the given username to the server whitelist.
|
||||||
|
|
||||||
# proper handling for having a min/max or not
|
|
||||||
whitelist-playercount-invalid = {$min ->
|
|
||||||
[0] The whitelist for this server only applies below {$max} players.
|
|
||||||
*[other] The whitelist for this server only applies above {$min} {$max ->
|
|
||||||
[2147483647] -> players, so you may be able to join later.
|
|
||||||
*[other] -> players and below {$max} players, so you may be able to join later.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
whitelist-not-whitelisted-rp = You are not whitelisted. To become whitelisted, visit our Discord (which can be found at https://spacestation14.io) and check the #rp-whitelist channel.
|
|
||||||
|
|
||||||
cmd-whitelistadd-desc = Adds the player with the given username to the server whitelist.
|
|
||||||
cmd-whitelistadd-help = Usage: whitelistadd <username or User ID>
|
cmd-whitelistadd-help = Usage: whitelistadd <username or User ID>
|
||||||
cmd-whitelistadd-existing = {$username} is already on the whitelist!
|
cmd-whitelistadd-existing = {$username} is already on the whitelist!
|
||||||
cmd-whitelistadd-added = {$username} added to the whitelist
|
cmd-whitelistadd-added = {$username} added to the whitelist
|
||||||
@@ -40,8 +28,30 @@ panic-bunker-account-denied-reason = This server is in panic bunker mode, often
|
|||||||
panic-bunker-account-reason-account = Your Space Station 14 account is too new. It must be older than {$minutes} minutes
|
panic-bunker-account-reason-account = Your Space Station 14 account is too new. It must be older than {$minutes} minutes
|
||||||
panic-bunker-account-reason-overall = Your overall playtime on the server must be greater than {$minutes} $minutes
|
panic-bunker-account-reason-overall = Your overall playtime on the server must be greater than {$minutes} $minutes
|
||||||
|
|
||||||
|
whitelist-playtime = You do not have enough playtime to join this server. You need at least {$minutes} minutes of playtime to join this server.
|
||||||
|
whitelist-player-count = This server is currently not accepting players. Please try again later.
|
||||||
|
whitelist-notes = You currently have too many admin notes to join this server. You can check your notes by typing /adminremarks in chat.
|
||||||
|
whitelist-manual = You are not whitelisted on this server.
|
||||||
|
whitelist-blacklisted = You are blacklisted from this server.
|
||||||
|
whitelist-always-deny = You are not allowed to join this server.
|
||||||
|
whitelist-fail-prefix = Not whitelisted: {$msg}
|
||||||
|
whitelist-misconfigured = The server is misconfigured and is not accepting players. Please contact the server owner and try again later.
|
||||||
|
|
||||||
|
cmd-blacklistadd-desc = Adds the player with the given username to the server blacklist.
|
||||||
|
cmd-blacklistadd-help = Usage: blacklistadd <username>
|
||||||
|
cmd-blacklistadd-existing = {$username} is already on the blacklist!
|
||||||
|
cmd-blacklistadd-added = {$username} added to the blacklist
|
||||||
|
cmd-blacklistadd-not-found = Unable to find '{$username}'
|
||||||
|
cmd-blacklistadd-arg-player = [player]
|
||||||
|
|
||||||
|
cmd-blacklistremove-desc = Removes the player with the given username from the server blacklist.
|
||||||
|
cmd-blacklistremove-help = Usage: blacklistremove <username>
|
||||||
|
cmd-blacklistremove-existing = {$username} is not on the blacklist!
|
||||||
|
cmd-blacklistremove-removed = {$username} removed from the blacklist
|
||||||
|
cmd-blacklistremove-not-found = Unable to find '{$username}'
|
||||||
|
cmd-blacklistremove-arg-player = [player]
|
||||||
|
|
||||||
baby-jail-account-denied = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun!
|
baby-jail-account-denied = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun!
|
||||||
baby-jail-account-denied-reason = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun! Reason: "{$reason}"
|
baby-jail-account-denied-reason = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun! Reason: "{$reason}"
|
||||||
baby-jail-account-reason-account = Your Space Station 14 account is too old. It must be younger than {$minutes} minutes
|
baby-jail-account-reason-account = Your Space Station 14 account is too old. It must be younger than {$minutes} minutes
|
||||||
baby-jail-account-reason-overall = Your overall playtime on the server must be younger than {$minutes} $minutes
|
baby-jail-account-reason-overall = Your overall playtime on the server must be younger than {$minutes} $minutes
|
||||||
|
|
||||||
|
|||||||
7
Resources/Prototypes/whitelists.yml
Normal file
7
Resources/Prototypes/whitelists.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- type: playerConnectionWhitelist
|
||||||
|
id: basicWhitelist # Basic whitelist using only the ManualWhitelist condition
|
||||||
|
conditions:
|
||||||
|
- !type:ConditionManualWhitelistMembership
|
||||||
|
action: Allow # Allow connection if matched
|
||||||
|
- !type:ConditionAlwaysMatch # Condition that always matches
|
||||||
|
action: Deny # Deny connection if matched
|
||||||
39
Resources/Prototypes/wizardsDenWhitelists.yml
Normal file
39
Resources/Prototypes/wizardsDenWhitelists.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# This is the whitelist used for Wizard's Den Salamander
|
||||||
|
|
||||||
|
- type: playerConnectionWhitelist
|
||||||
|
id: salamanderMrpWhitelist
|
||||||
|
conditions:
|
||||||
|
- !type:ConditionManualBlacklistMembership # Deny blacklisted (MRP ban)
|
||||||
|
action: Deny
|
||||||
|
- !type:ConditionNotesPlaytimeRange # Deny for high severity notes in the last 30 days
|
||||||
|
includeExpired: false
|
||||||
|
minimumSeverity: 3 # High
|
||||||
|
minimumNotes: 1
|
||||||
|
range: 30 # 30 days
|
||||||
|
action: Deny
|
||||||
|
includeSecret: false
|
||||||
|
- !type:ConditionNotesPlaytimeRange # Deny for >=2 medium severity notes in the last 14 days
|
||||||
|
includeExpired: false
|
||||||
|
minimumSeverity: 2 # Medium
|
||||||
|
minimumNotes: 1
|
||||||
|
range: 14 # 14 Days
|
||||||
|
action: Deny
|
||||||
|
includeSecret: false
|
||||||
|
- !type:ConditionNotesPlaytimeRange # Deny for >=3 low severity notes in the last 14 days
|
||||||
|
includeExpired: false
|
||||||
|
minimumSeverity: 1 # Low
|
||||||
|
minimumNotes: 3
|
||||||
|
range: 14 # 14 Days
|
||||||
|
action: Deny
|
||||||
|
includeSecret: false
|
||||||
|
- !type:ConditionManualWhitelistMembership # Allow whitelisted players
|
||||||
|
action: Allow
|
||||||
|
- !type:ConditionPlayerCount # Allow when <= 15 players are online
|
||||||
|
minimumPlayers: 0
|
||||||
|
maximumPlayers: 15
|
||||||
|
action: Allow
|
||||||
|
#- !type:ConditionPlaytime
|
||||||
|
# minimumPlaytime: 1200 # 20 hours to be whitelisted
|
||||||
|
# action: Deny
|
||||||
|
- !type:ConditionAlwaysMatch
|
||||||
|
action: Deny
|
||||||
Reference in New Issue
Block a user