Skip to content

Commit 436fca1

Browse files
zonicalroflmuffin
andauthored
fix(admin): root flags in RequirePermissionsOr & registering admin groups (#700)
Co-authored-by: Michael Wilson <[email protected]>
1 parent cfab6af commit 436fca1

File tree

8 files changed

+220
-76
lines changed

8 files changed

+220
-76
lines changed

managed/CounterStrikeSharp.API.Tests/AdminTests.cs

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,33 @@ public void ShouldAddFlagsAtRuntime()
8585
[Fact]
8686
public void ShouldAddPlayerToGroup()
8787
{
88+
// AddPlayerToGroup will create a new AdminData instance here, as "STEAM_0:1:3" does not
89+
// exist internally at this stage. Afterwards, AddPlayerToGroup will add "#css/root" as
90+
// a group.
8891
AdminManager.AddPlayerToGroup(new SteamID("STEAM_0:1:3"), "#css/root");
8992
var adminData = AdminManager.GetPlayerAdminData(new SteamID("STEAM_0:1:3"));
9093
Assert.NotNull(adminData);
94+
Assert.True(adminData.Groups.Any());
9195
Assert.Equal("#css/root", adminData.Groups.Single());
9296
}
93-
97+
98+
[Fact]
99+
public void ShouldAddPlayerToGroupAtRuntime()
100+
{
101+
// Similar to ShouldPlayerAddToGroup, but different in that we create AdminData
102+
// manually first, then add the player into the group.
103+
// Aimed to resolve an issue raised on Discord by r3troattack.
104+
105+
AdminManager.CreateAdminData((SteamID)76561197960265999);
106+
Assert.False(AdminManager.PlayerInGroup((SteamID)76561197960265999, "#css/root"));
107+
AdminManager.AddPlayerToGroup((SteamID)76561197960265999, "#css/root");
108+
109+
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265999);
110+
Assert.NotNull(adminData);
111+
Assert.True(adminData.Groups.Any());
112+
Assert.Equal("#css/root", adminData.Groups.Single());
113+
}
114+
94115
[Fact]
95116
public void ShouldAddPlayerPermissionOverridesAtRuntime()
96117
{
@@ -116,4 +137,108 @@ public void ShouldAddCommandPermissionOverridesWithEmpty()
116137
Assert.True(AdminManager.CommandIsOverriden("runtime_command_b"));
117138
Assert.False(AdminManager.GetPermissionOverrides("runtime_command_b").Any());
118139
}
119-
}
140+
141+
[Fact]
142+
public void ShouldAllowRootFlagInRequiresPermissions()
143+
{
144+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265732, "@css/root"));
145+
AdminManager.AddPlayerPermissions((SteamID)76561197960265732, "@css/root");
146+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265732, "@css/root"));
147+
148+
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265732);
149+
Assert.NotNull(adminData);
150+
Assert.True(adminData.DomainHasRootFlag("css"));
151+
152+
var requirePerms = new RequiresPermissions("@css/ban");
153+
Assert.NotNull(requirePerms);
154+
Assert.True(requirePerms.CanExecuteCommand((SteamID)76561197960265732));
155+
}
156+
157+
[Fact]
158+
public void ShouldRequiresPermissionsPass()
159+
{
160+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265738, "@css/ban"));
161+
AdminManager.AddPlayerPermissions((SteamID)76561197960265738, "@css/ban");
162+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265738, "@css/ban"));
163+
164+
var requirePerms = new RequiresPermissions("@css/ban");
165+
Assert.NotNull(requirePerms);
166+
Assert.True(requirePerms.CanExecuteCommand((SteamID)76561197960265738));
167+
}
168+
169+
[Fact]
170+
public void ShouldRequiresPermissionsOrPassSameDomain()
171+
{
172+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265739, "@css/ban"));
173+
AdminManager.AddPlayerPermissions((SteamID)76561197960265739, "@css/ban");
174+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265739, "@css/ban"));
175+
176+
var requirePerms = new RequiresPermissionsOr("@css/ban", "@css/kick");
177+
Assert.NotNull(requirePerms);
178+
Assert.True(requirePerms.CanExecuteCommand((SteamID)76561197960265739));
179+
}
180+
181+
[Fact]
182+
public void ShouldRequiresPermissionsOrPassDifferentDomains()
183+
{
184+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265740, "@css/ban"));
185+
AdminManager.AddPlayerPermissions((SteamID)76561197960265740, "@css/ban");
186+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265740, "@css/ban"));
187+
188+
var requirePerms = new RequiresPermissionsOr("@css/ban", "@domain2/flag");
189+
Assert.NotNull(requirePerms);
190+
Assert.True(requirePerms.CanExecuteCommand((SteamID)76561197960265740));
191+
}
192+
193+
[Fact]
194+
public void ShouldRequiresPermissionsCheckFail()
195+
{
196+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265735, "@css/ban"));
197+
AdminManager.AddPlayerPermissions((SteamID)76561197960265735, "@css/ban");
198+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265735, "@css/ban"));
199+
200+
var requirePerms = new RequiresPermissions("@css/kick");
201+
Assert.NotNull(requirePerms);
202+
Assert.False(requirePerms.CanExecuteCommand((SteamID)76561197960265735));
203+
}
204+
205+
[Fact]
206+
public void ShouldRequiresPermissionsOrCheckFailSameDomain()
207+
{
208+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265736, "@css/ban"));
209+
AdminManager.AddPlayerPermissions((SteamID)76561197960265736, "@css/ban");
210+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265736, "@css/ban"));
211+
212+
var requirePerms = new RequiresPermissionsOr("@css/kick", "@css/vote");
213+
Assert.NotNull(requirePerms);
214+
Assert.False(requirePerms.CanExecuteCommand((SteamID)76561197960265736));
215+
}
216+
217+
[Fact]
218+
public void ShouldRequiresPermissionsOrCheckFailDifferentDomains()
219+
{
220+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265737, "@css/ban"));
221+
AdminManager.AddPlayerPermissions((SteamID)76561197960265737, "@css/ban");
222+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265737, "@css/ban"));
223+
224+
var requirePerms = new RequiresPermissionsOr("@css/kick", "@domain2/flag");
225+
Assert.NotNull(requirePerms);
226+
Assert.False(requirePerms.CanExecuteCommand((SteamID)76561197960265737));
227+
}
228+
229+
[Fact]
230+
public void ShouldAllowRootFlagInRequiresPermissionsOr()
231+
{
232+
Assert.False(AdminManager.PlayerHasPermissions((SteamID)76561197960265733, "@css/root"));
233+
AdminManager.AddPlayerPermissions((SteamID)76561197960265733, "@css/root");
234+
Assert.True(AdminManager.PlayerHasPermissions((SteamID)76561197960265733, "@css/root"));
235+
236+
var adminData = AdminManager.GetPlayerAdminData((SteamID)76561197960265733);
237+
Assert.NotNull(adminData);
238+
Assert.True(adminData.DomainHasRootFlag("css"));
239+
240+
var requirePerms = new RequiresPermissionsOr("@css/ban", "@css/kick", "@domain2/flag");
241+
Assert.NotNull(requirePerms);
242+
Assert.True(requirePerms.CanExecuteCommand((SteamID)76561197960265733));
243+
}
244+
}

managed/CounterStrikeSharp.API/Core/Commands/CommandManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private void HandleCommandInternal(int playerSlot, IntPtr commandInfo)
117117
}
118118

119119
attr.Command = name;
120-
if (!attr.CanExecuteCommand(caller))
120+
if (!attr.CanExecuteCommand(caller.AuthorizedSteamID))
121121
{
122122
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
123123
Application.Localizer["Missing permissions"] :
@@ -166,4 +166,4 @@ private void HandleCommandInternal(int playerSlot, IntPtr commandInfo)
166166
}
167167
}
168168
}
169-
}
169+
}

managed/CounterStrikeSharp.API/Modules/Admin/AdminGroup.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Numerics;
6-
using System.Text;
7-
using System.Text.Json;
1+
using System.Text.Json;
82
using System.Text.Json.Serialization;
9-
10-
using CounterStrikeSharp.API.Core;
113
using CounterStrikeSharp.API.Modules.Entities;
12-
using CounterStrikeSharp.API.Modules.Utils;
134

145
namespace CounterStrikeSharp.API.Modules.Admin
156
{
@@ -157,22 +148,28 @@ public static void AddPlayerToGroup(SteamID? steamId, params string[] groups)
157148
{
158149
if (steamId == null) return;
159150
var data = GetPlayerAdminData(steamId);
151+
160152
if (data == null)
161153
{
162154
data = new AdminData()
163155
{
164156
Identity = steamId.SteamId64.ToString(),
165-
Flags = new(),
166-
Groups = new(groups)
157+
Flags = new()
167158
};
168159
}
169160

170161
foreach (var group in groups)
171162
{
163+
// If we're already in this group, we don't need to assign permissions again.
164+
if (data.Groups.Contains(group)) continue;
165+
166+
data.Groups.Add(group);
167+
168+
// If this group exists internally, apply group permissions to the player.
172169
if (Groups.TryGetValue(group, out var groupDef))
173170
{
174171
AddPlayerPermissions(steamId, groupDef.Flags.ToArray());
175-
groupDef.CommandOverrides.ToList().ForEach(x => data.CommandOverrides[x.Key] = x.Value);
172+
groupDef.CommandOverrides.ToList().ForEach(x => data.CommandOverrides[x.Key] = x.Value);
176173
}
177174
}
178175
Admins[steamId] = data;

managed/CounterStrikeSharp.API/Modules/Admin/AdminManager.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
using CounterStrikeSharp.API.Modules.Commands;
2-
using CounterStrikeSharp.API.Modules.Utils;
3-
using System.Linq;
42
using System.Reflection;
53
using CounterStrikeSharp.API.Core.Commands;
6-
using CounterStrikeSharp.API.Core.Logging;
7-
using Microsoft.Extensions.Logging;
84

95
namespace CounterStrikeSharp.API.Modules.Admin
106
{
@@ -100,4 +96,4 @@ private static void ListAdminOverridesCommand(CCSPlayerController? player, Comma
10096
}
10197
}
10298
}
103-
}
99+
}

managed/CounterStrikeSharp.API/Modules/Admin/AdminPermissions.cs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
1-
using System;
2-
using System.IO;
3-
using System.Collections.Generic;
4-
using System.Text.Json.Serialization;
1+
using System.Text.Json.Serialization;
52
using System.Text.Json;
63
using CounterStrikeSharp.API.Modules.Entities;
7-
using System.Linq;
84
using CounterStrikeSharp.API.Core.Logging;
95
using Microsoft.Extensions.Logging;
10-
using System.Text.Json.Nodes;
11-
using System.Numerics;
12-
using CounterStrikeSharp.API.Modules.Utils;
13-
using System.Diagnostics.Eventing.Reader;
146

157
namespace CounterStrikeSharp.API.Modules.Admin
168
{
@@ -23,7 +15,7 @@ public partial class AdminData
2315
[JsonPropertyName("immunity")] public uint Immunity { get; set; } = 0;
2416
[JsonPropertyName("command_overrides")] public Dictionary<string, bool> CommandOverrides { get; init; } = new();
2517

26-
// Key is the domain of the flag "e.g "css, os, kzsurf"). This should NOT include the @ character.
18+
// Key is the domain of the flag "e.g "css, os, kzsurf". This should NOT include the @ character.
2719
// Value is a hashmap of the flags inside of the domain (e.g "@css/generic")
2820
public Dictionary<string, HashSet<string>> Flags { get; init; } = new();
2921

@@ -81,7 +73,9 @@ public void AddFlags(HashSet<string> flags)
8173
{
8274
Flags[domain] = new HashSet<string>();
8375
}
84-
Flags[domain].UnionWith(flags.Where(flag => flag.StartsWith(PermissionCharacters.UserPermissionChar + domain + '/')).ToHashSet());
76+
77+
var matchingFlags = flags.Where(flag => flag.StartsWith(PermissionCharacters.UserPermissionChar + domain + '/'));
78+
Flags[domain].UnionWith(matchingFlags.ToHashSet());
8579
}
8680
}
8781

@@ -166,14 +160,43 @@ public static void LoadAdminData(string adminDataPath)
166160
}
167161
}
168162

169-
/// <summary>
163+
/// <summary>
164+
/// Creates a blank AdminData object for a player. This is not saved to "configs/admins.json"
165+
/// </summary>
166+
/// <param name="player">Player controller.</param>
167+
/// <returns>Blank AdminData class.</returns>
168+
public static AdminData? CreateAdminData(CCSPlayerController? player)
169+
{
170+
if (player == null || player is { IsValid: false }) return null;
171+
return CreateAdminData(player.AuthorizedSteamID);
172+
}
173+
174+
/// <summary>
175+
/// Creates a blank AdminData object for a player. This is not saved to "configs/admins.json"
176+
/// </summary>
177+
/// <param name="steamID">SteamID object of the player.</param>
178+
/// <returns>Blank AdminData class.</returns>
179+
public static AdminData? CreateAdminData(SteamID? steamID)
180+
{
181+
if (steamID is null) return null;
182+
var adminData = new AdminData()
183+
{
184+
Identity = steamID.SteamId64.ToString()
185+
};
186+
187+
Admins[steamID] = adminData;
188+
189+
return adminData;
190+
}
191+
192+
/// <summary>
170193
/// Grabs the admin data for a player that was loaded from "configs/admins.json" and "configs/admins_groups.json".
171194
/// </summary>
172195
/// <param name="player">Player controller</param>
173196
/// <returns>AdminData class if data found, null if not.</returns>
174197
public static AdminData? GetPlayerAdminData(CCSPlayerController? player)
175198
{
176-
if (player == null) return null;
199+
if (player == null || player is { IsValid : false }) return null;
177200
return GetPlayerAdminData(player.AuthorizedSteamID);
178201
}
179202

@@ -194,7 +217,7 @@ public static void LoadAdminData(string adminDataPath)
194217
/// <param name="player">Player controller</param>
195218
public static void RemovePlayerAdminData(CCSPlayerController? player)
196219
{
197-
if (player == null) return;
220+
if (player == null || player is { IsValid: false }) return;
198221
RemovePlayerAdminData(player.AuthorizedSteamID);
199222
}
200223

@@ -208,6 +231,14 @@ public static void RemovePlayerAdminData(SteamID? steamId)
208231
Admins.Remove(steamId);
209232
}
210233

234+
/// <summary>
235+
/// Removes all locally stored admin data. This is not saved to "configs/admins.json"
236+
/// </summary>
237+
public static void FlushLocalAdminData()
238+
{
239+
Admins.Clear();
240+
}
241+
211242
#region Command Permission Checks
212243

213244
/// <summary>

managed/CounterStrikeSharp.API/Modules/Admin/BaseRequiresPermissions.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
using CounterStrikeSharp.API.Core;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using CounterStrikeSharp.API.Modules.Entities;
1+
using CounterStrikeSharp.API.Modules.Entities;
62

73
namespace CounterStrikeSharp.API.Modules.Admin
84
{
@@ -27,19 +23,29 @@ public BaseRequiresPermissions(params string[] permissions)
2723
Command = "";
2824
}
2925

30-
public virtual bool CanExecuteCommand(CCSPlayerController? caller)
26+
public virtual bool CanExecuteCommand(SteamID? steamID)
3127
{
28+
if (steamID is null) return false;
29+
30+
var adminData = AdminManager.GetPlayerAdminData(steamID);
31+
if (adminData is null) return false;
32+
3233
// If we have a command in the "command_overrides" section in "configs/admins.json",
3334
// we skip the checks below and just return the defined value.
34-
if (caller?.AuthorizedSteamID == null) return false;
35-
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
36-
if (adminData == null) return false;
3735
if (adminData.CommandOverrides.TryGetValue(Command, out var command))
3836
{
3937
return command;
4038
}
4139

4240
return true;
4341
}
42+
43+
public virtual bool CanExecuteCommand(CCSPlayerController? caller)
44+
{
45+
if ( caller is null || caller is { IsValid: false } ) return false;
46+
if ( caller.AuthorizedSteamID is null ) return false;
47+
48+
return CanExecuteCommand(caller.AuthorizedSteamID);
49+
}
4450
}
4551
}

0 commit comments

Comments
 (0)