Persist deadmin to database, add admin suspension system (#34048)
This commit is contained in:
committed by
GitHub
parent
47042cc8dd
commit
c2e050ced0
@@ -130,6 +130,7 @@ namespace Content.Client.Administration.UI
|
||||
}
|
||||
|
||||
var title = string.IsNullOrWhiteSpace(popup.TitleEdit.Text) ? null : popup.TitleEdit.Text;
|
||||
var suspended = popup.SuspendedCheckbox.Pressed;
|
||||
|
||||
if (popup.SourceData is { } src)
|
||||
{
|
||||
@@ -139,7 +140,8 @@ namespace Content.Client.Administration.UI
|
||||
Title = title,
|
||||
PosFlags = pos,
|
||||
NegFlags = neg,
|
||||
RankId = rank
|
||||
RankId = rank,
|
||||
Suspended = suspended,
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -152,7 +154,8 @@ namespace Content.Client.Administration.UI
|
||||
Title = title,
|
||||
PosFlags = pos,
|
||||
NegFlags = neg,
|
||||
RankId = rank
|
||||
RankId = rank,
|
||||
Suspended = suspended,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,7 +174,7 @@ namespace Content.Client.Administration.UI
|
||||
{
|
||||
Id = src,
|
||||
Flags = flags,
|
||||
Name = name
|
||||
Name = name,
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -351,6 +354,7 @@ namespace Content.Client.Administration.UI
|
||||
public readonly OptionButton RankButton;
|
||||
public readonly Button SaveButton;
|
||||
public readonly Button? RemoveButton;
|
||||
public readonly CheckBox SuspendedCheckbox;
|
||||
|
||||
public readonly Dictionary<AdminFlags, (Button inherit, Button sub, Button plus)> FlagButtons
|
||||
= new();
|
||||
@@ -381,6 +385,12 @@ namespace Content.Client.Administration.UI
|
||||
RankButton = new OptionButton();
|
||||
SaveButton = new Button { Text = Loc.GetString("permissions-eui-edit-admin-window-save-button"), HorizontalAlignment = HAlignment.Right };
|
||||
|
||||
SuspendedCheckbox = new CheckBox
|
||||
{
|
||||
Text = Loc.GetString("permissions-eui-edit-admin-window-suspended"),
|
||||
Pressed = data?.Suspended ?? false,
|
||||
};
|
||||
|
||||
RankButton.AddItem(Loc.GetString("permissions-eui-edit-admin-window-no-rank-button"), NoRank);
|
||||
foreach (var (rId, rank) in ui._ranks)
|
||||
{
|
||||
@@ -488,7 +498,8 @@ namespace Content.Client.Administration.UI
|
||||
{
|
||||
nameControl,
|
||||
TitleEdit,
|
||||
RankButton
|
||||
RankButton,
|
||||
SuspendedCheckbox,
|
||||
}
|
||||
},
|
||||
permGrid
|
||||
|
||||
2084
Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs
generated
Normal file
2084
Content.Server.Database/Migrations/Postgres/20241223235939_AdminStatus.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AdminStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deadminned",
|
||||
table: "admin",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "suspended",
|
||||
table: "admin",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deadminned",
|
||||
table: "admin");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "suspended",
|
||||
table: "admin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("admin_rank_id");
|
||||
|
||||
b.Property<bool>("Deadminned")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deadminned");
|
||||
|
||||
b.Property<bool>("Suspended")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("suspended");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("title");
|
||||
|
||||
2007
Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs
generated
Normal file
2007
Content.Server.Database/Migrations/Sqlite/20241223235932_AdminStatus.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AdminStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deadminned",
|
||||
table: "admin",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "suspended",
|
||||
table: "admin",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deadminned",
|
||||
table: "admin");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "suspended",
|
||||
table: "admin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("admin_rank_id");
|
||||
|
||||
b.Property<bool>("Deadminned")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("deadminned");
|
||||
|
||||
b.Property<bool>("Suspended")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("suspended");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("title");
|
||||
|
||||
@@ -610,6 +610,16 @@ namespace Content.Server.Database
|
||||
[Key] public Guid UserId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the admin is voluntarily deadminned. They can re-admin at any time.
|
||||
/// </summary>
|
||||
public bool Deadminned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the admin is suspended by an admin with <c>PERMISSIONS</c>. They will not have in-game permissions.
|
||||
/// </summary>
|
||||
public bool Suspended { get; set; }
|
||||
|
||||
public int? AdminRankId { get; set; }
|
||||
public AdminRank? AdminRank { get; set; }
|
||||
public List<AdminFlag> Flags { get; set; } = default!;
|
||||
|
||||
@@ -91,14 +91,29 @@ namespace Content.Server.Administration.Managers
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)));
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-normal-player-message"));
|
||||
|
||||
var plyData = session.ContentData()!;
|
||||
plyData.ExplicitlyDeadminned = true;
|
||||
UpdateDatabaseDeadminnedState(session, true);
|
||||
reg.Data.Active = false;
|
||||
|
||||
SendPermsChangedEvent(session);
|
||||
UpdateAdminStatus(session);
|
||||
}
|
||||
|
||||
private async void UpdateDatabaseDeadminnedState(ICommonSession player, bool newState)
|
||||
{
|
||||
try
|
||||
{
|
||||
// NOTE: This function gets called if you deadmin/readmin from a transient admin status.
|
||||
// (e.g. loginlocal)
|
||||
// In which case there may not be a database record.
|
||||
// The DB function handles this scenario fine, but it's worth noting.
|
||||
await _dbManager.UpdateAdminDeadminnedAsync(player.UserId, newState);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error("Failed to save deadmin state to database for {Admin}", player.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stealth(ICommonSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
@@ -151,8 +166,7 @@ namespace Content.Server.Administration.Managers
|
||||
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-became-admin-message"));
|
||||
|
||||
var plyData = session.ContentData()!;
|
||||
plyData.ExplicitlyDeadminned = false;
|
||||
UpdateDatabaseDeadminnedState(session, false);
|
||||
reg.Data.Active = true;
|
||||
|
||||
if (!reg.Data.Stealth)
|
||||
@@ -208,14 +222,14 @@ namespace Content.Server.Administration.Managers
|
||||
curAdmin.IsSpecialLogin = special;
|
||||
curAdmin.RankId = rankId;
|
||||
curAdmin.Data = aData;
|
||||
}
|
||||
|
||||
if (!player.ContentData()!.ExplicitlyDeadminned)
|
||||
if (curAdmin.Data.Active)
|
||||
{
|
||||
aData.Active = true;
|
||||
|
||||
_chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message"));
|
||||
}
|
||||
}
|
||||
|
||||
if (player.ContentData()!.Stealthed)
|
||||
{
|
||||
@@ -381,10 +395,8 @@ namespace Content.Server.Administration.Managers
|
||||
if (session.ContentData()!.Stealthed)
|
||||
reg.Data.Stealth = true;
|
||||
|
||||
if (!session.ContentData()!.ExplicitlyDeadminned)
|
||||
if (reg.Data.Active)
|
||||
{
|
||||
reg.Data.Active = true;
|
||||
|
||||
if (_cfg.GetCVar(CCVars.AdminAnnounceLogin))
|
||||
{
|
||||
if (reg.Data.Stealth)
|
||||
@@ -430,6 +442,7 @@ namespace Content.Server.Administration.Managers
|
||||
{
|
||||
Title = Loc.GetString("admin-manager-admin-data-host-title"),
|
||||
Flags = AdminFlagsHelper.Everything,
|
||||
Active = true,
|
||||
};
|
||||
|
||||
return (data, null, true);
|
||||
@@ -444,6 +457,12 @@ namespace Content.Server.Administration.Managers
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dbData.Suspended)
|
||||
{
|
||||
// Suspended admins don't count.
|
||||
return null;
|
||||
}
|
||||
|
||||
var flags = AdminFlags.None;
|
||||
|
||||
if (dbData.AdminRank != null)
|
||||
@@ -466,7 +485,8 @@ namespace Content.Server.Administration.Managers
|
||||
|
||||
var data = new AdminData
|
||||
{
|
||||
Flags = flags
|
||||
Flags = flags,
|
||||
Active = !dbData.Deadminned,
|
||||
};
|
||||
|
||||
if (dbData.Title != null && _cfg.GetCVar(CCVars.AdminUseCustomNamesAdminRank))
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace Content.Server.Administration.UI
|
||||
Title = p.a.Title,
|
||||
RankId = p.a.AdminRankId,
|
||||
UserId = new NetUserId(p.a.UserId),
|
||||
UserName = p.lastUserName
|
||||
UserName = p.lastUserName,
|
||||
Suspended = p.a.Suspended,
|
||||
}).ToArray(),
|
||||
|
||||
AdminRanks = _adminRanks.ToDictionary(a => a.Id, a => new PermissionsEuiState.AdminRankData
|
||||
@@ -255,6 +256,7 @@ namespace Content.Server.Administration.UI
|
||||
admin.Title = ua.Title;
|
||||
admin.AdminRankId = ua.RankId;
|
||||
admin.Flags = GenAdminFlagList(ua.PosFlags, ua.NegFlags);
|
||||
admin.Suspended = ua.Suspended;
|
||||
|
||||
await _db.UpdateAdminAsync(admin);
|
||||
|
||||
@@ -335,7 +337,8 @@ namespace Content.Server.Administration.UI
|
||||
Flags = GenAdminFlagList(ca.PosFlags, ca.NegFlags),
|
||||
AdminRankId = ca.RankId,
|
||||
UserId = userId.UserId,
|
||||
Title = ca.Title
|
||||
Title = ca.Title,
|
||||
Suspended = ca.Suspended,
|
||||
};
|
||||
|
||||
await _db.AddAdminAsync(admin);
|
||||
|
||||
@@ -751,6 +751,20 @@ namespace Content.Server.Database
|
||||
existing.Flags = admin.Flags;
|
||||
existing.Title = admin.Title;
|
||||
existing.AdminRankId = admin.AdminRankId;
|
||||
existing.Deadminned = admin.Deadminned;
|
||||
existing.Suspended = admin.Suspended;
|
||||
|
||||
await db.DbContext.SaveChangesAsync(cancel);
|
||||
}
|
||||
|
||||
public async Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
|
||||
var adminRecord = db.DbContext.Admin.Where(a => a.UserId == userId);
|
||||
await adminRecord.ExecuteUpdateAsync(
|
||||
set => set.SetProperty(p => p.Deadminned, deadminned),
|
||||
cancellationToken: cancel);
|
||||
|
||||
await db.DbContext.SaveChangesAsync(cancel);
|
||||
}
|
||||
|
||||
@@ -217,6 +217,16 @@ namespace Content.Server.Database
|
||||
Task AddAdminAsync(Admin admin, CancellationToken cancel = default);
|
||||
Task UpdateAdminAsync(Admin admin, CancellationToken cancel = default);
|
||||
|
||||
/// <summary>
|
||||
/// Update whether an admin has voluntarily deadminned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does nothing if the player is not an admin.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user ID of the admin.</param>
|
||||
/// <param name="deadminned">Whether the admin is deadminned or not.</param>
|
||||
Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default);
|
||||
|
||||
Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default);
|
||||
Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
||||
Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel = default);
|
||||
@@ -674,6 +684,12 @@ namespace Content.Server.Database
|
||||
return RunDbCommand(() => _db.UpdateAdminAsync(admin, cancel));
|
||||
}
|
||||
|
||||
public Task UpdateAdminDeadminnedAsync(NetUserId userId, bool deadminned, CancellationToken cancel = default)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.UpdateAdminDeadminnedAsync(userId, deadminned, cancel));
|
||||
}
|
||||
|
||||
public Task RemoveAdminRankAsync(int rankId, CancellationToken cancel = default)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Content.Shared.Administration
|
||||
public NetUserId UserId;
|
||||
public string? UserName;
|
||||
public string? Title;
|
||||
public bool Suspended;
|
||||
public AdminFlags PosFlags;
|
||||
public AdminFlags NegFlags;
|
||||
public int? RankId;
|
||||
@@ -41,6 +42,7 @@ namespace Content.Shared.Administration
|
||||
public AdminFlags PosFlags;
|
||||
public AdminFlags NegFlags;
|
||||
public int? RankId;
|
||||
public bool Suspended;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -57,6 +59,7 @@ namespace Content.Shared.Administration
|
||||
public AdminFlags PosFlags;
|
||||
public AdminFlags NegFlags;
|
||||
public int? RankId;
|
||||
public bool Suspended;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,12 +32,6 @@ public sealed class ContentPlayerData
|
||||
[ViewVariables, Access(typeof(SharedMindSystem), typeof(SharedGameTicker))]
|
||||
public EntityUid? Mind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the player is an admin and they explicitly de-adminned mid-game,
|
||||
/// so they should not regain admin if they reconnect.
|
||||
/// </summary>
|
||||
public bool ExplicitlyDeadminned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the admin will not show up in adminwho except to admins with the <see cref="AdminFlags.Stealth"/> flag.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ permissions-eui-edit-admin-window-title-edit-placeholder = Custom title, leave b
|
||||
permissions-eui-edit-admin-window-no-rank-button = No rank
|
||||
permissions-eui-edit-admin-rank-window-name-edit-placeholder = Rank name
|
||||
permissions-eui-edit-admin-title-control-text = none
|
||||
permissions-eui-edit-admin-window-suspended = Suspended?
|
||||
permissions-eui-edit-no-rank-text = none
|
||||
permissions-eui-edit-title-button = Edit
|
||||
permissions-eui-edit-admin-rank-button = Edit
|
||||
|
||||
Reference in New Issue
Block a user