Skip to content

Commit 3917e7c

Browse files
feat: Refactor authorization checks to use game-scoped claims and add unit tests for AdminActionsAuthHandler
1 parent 32ffc8a commit 3917e7c

3 files changed

Lines changed: 182 additions & 17 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using System.Security.Claims;
3+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
4+
using XtremeIdiots.Portal.Web.Auth.Constants;
5+
using XtremeIdiots.Portal.Web.Auth.Handlers;
6+
using XtremeIdiots.Portal.Web.Auth.Requirements;
7+
8+
namespace XtremeIdiots.Portal.Web.Tests.Auth.Handlers;
9+
10+
public class AdminActionsAuthHandlerTests
11+
{
12+
[Fact]
13+
public async Task HandleAsync_Create_SucceedsForCod4ClaimOnCod4xResource()
14+
{
15+
var requirement = new AdminActionsCreate();
16+
var user = CreateUser(new Claim(UserProfileClaimType.GameAdmin, GameType.CallOfDuty4.ToString()));
17+
var context = new AuthorizationHandlerContext([requirement], user, (GameType.CallOfDuty4x, AdminActionType.Ban));
18+
19+
var sut = new AdminActionsAuthHandler();
20+
await sut.HandleAsync(context);
21+
22+
Assert.True(context.HasSucceeded);
23+
}
24+
25+
[Fact]
26+
public async Task HandleAsync_Create_SucceedsForCod4PermissionClaimOnCod4xResource()
27+
{
28+
var requirement = new AdminActionsCreate();
29+
var user = CreateUser(new Claim(AuthPolicies.AdminActions_Create, GameType.CallOfDuty4.ToString()));
30+
var context = new AuthorizationHandlerContext([requirement], user, (GameType.CallOfDuty4x, AdminActionType.Ban));
31+
32+
var sut = new AdminActionsAuthHandler();
33+
await sut.HandleAsync(context);
34+
35+
Assert.True(context.HasSucceeded);
36+
}
37+
38+
[Fact]
39+
public async Task HandleAsync_Create_SucceedsForCod4xClaimOnCod4Resource()
40+
{
41+
var requirement = new AdminActionsCreate();
42+
var user = CreateUser(new Claim(UserProfileClaimType.GameAdmin, GameType.CallOfDuty4x.ToString()));
43+
var context = new AuthorizationHandlerContext([requirement], user, (GameType.CallOfDuty4, AdminActionType.Ban));
44+
45+
var sut = new AdminActionsAuthHandler();
46+
await sut.HandleAsync(context);
47+
48+
Assert.True(context.HasSucceeded);
49+
}
50+
51+
[Fact]
52+
public async Task HandleAsync_Create_DoesNotSucceedForDifferentGame()
53+
{
54+
var requirement = new AdminActionsCreate();
55+
var user = CreateUser(new Claim(UserProfileClaimType.GameAdmin, GameType.Insurgency.ToString()));
56+
var context = new AuthorizationHandlerContext([requirement], user, (GameType.CallOfDuty4x, AdminActionType.Ban));
57+
58+
var sut = new AdminActionsAuthHandler();
59+
await sut.HandleAsync(context);
60+
61+
Assert.False(context.HasSucceeded);
62+
}
63+
64+
[Fact]
65+
public async Task HandleAsync_EditObservation_SucceedsForCod4ModeratorOnCod4xWhenOwner()
66+
{
67+
var requirement = new AdminActionsEdit();
68+
var ownerId = "owner-1";
69+
var user = CreateUser(
70+
new Claim(UserProfileClaimType.Moderator, GameType.CallOfDuty4.ToString()),
71+
new Claim(UserProfileClaimType.XtremeIdiotsId, ownerId));
72+
73+
var context = new AuthorizationHandlerContext(
74+
[requirement],
75+
user,
76+
(GameType.CallOfDuty4x, AdminActionType.Observation, ownerId));
77+
78+
var sut = new AdminActionsAuthHandler();
79+
await sut.HandleAsync(context);
80+
81+
Assert.True(context.HasSucceeded);
82+
}
83+
84+
[Fact]
85+
public async Task HandleAsync_EditObservation_DoesNotSucceedForCod4ModeratorOnCod4xWhenNotOwner()
86+
{
87+
var requirement = new AdminActionsEdit();
88+
var ownerId = "owner-1";
89+
var user = CreateUser(
90+
new Claim(UserProfileClaimType.Moderator, GameType.CallOfDuty4.ToString()),
91+
new Claim(UserProfileClaimType.XtremeIdiotsId, "different-owner"));
92+
93+
var context = new AuthorizationHandlerContext(
94+
[requirement],
95+
user,
96+
(GameType.CallOfDuty4x, AdminActionType.Observation, ownerId));
97+
98+
var sut = new AdminActionsAuthHandler();
99+
await sut.HandleAsync(context);
100+
101+
Assert.False(context.HasSucceeded);
102+
}
103+
104+
[Fact]
105+
public async Task HandleAsync_Lift_SucceedsForCod4GameAdminOnCod4xWhenOwner()
106+
{
107+
var requirement = new AdminActionsLift();
108+
var ownerId = "owner-2";
109+
var user = CreateUser(
110+
new Claim(UserProfileClaimType.GameAdmin, GameType.CallOfDuty4.ToString()),
111+
new Claim(UserProfileClaimType.XtremeIdiotsId, ownerId));
112+
113+
var context = new AuthorizationHandlerContext(
114+
[requirement],
115+
user,
116+
(GameType.CallOfDuty4x, ownerId));
117+
118+
var sut = new AdminActionsAuthHandler();
119+
await sut.HandleAsync(context);
120+
121+
Assert.True(context.HasSucceeded);
122+
}
123+
124+
[Fact]
125+
public async Task HandleAsync_Lift_DoesNotSucceedForCod4GameAdminOnCod4xWhenNotOwner()
126+
{
127+
var requirement = new AdminActionsLift();
128+
var ownerId = "owner-2";
129+
var user = CreateUser(
130+
new Claim(UserProfileClaimType.GameAdmin, GameType.CallOfDuty4.ToString()),
131+
new Claim(UserProfileClaimType.XtremeIdiotsId, "different-owner"));
132+
133+
var context = new AuthorizationHandlerContext(
134+
[requirement],
135+
user,
136+
new Tuple<GameType, string>(GameType.CallOfDuty4x, ownerId));
137+
138+
var sut = new AdminActionsAuthHandler();
139+
await sut.HandleAsync(context);
140+
141+
Assert.False(context.HasSucceeded);
142+
}
143+
144+
private static ClaimsPrincipal CreateUser(params Claim[] claims)
145+
{
146+
var identity = new ClaimsIdentity(claims, authenticationType: "TestAuthType");
147+
return new ClaimsPrincipal(identity);
148+
}
149+
}

src/XtremeIdiots.Portal.Web/Auth/Handlers/AdminActionsAuthHandler.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ private static void HandleLift(AuthorizationHandlerContext context, IAuthorizati
113113
if (context.Resource is Tuple<GameType, string> refTuple)
114114
{
115115
BaseAuthorizationHelper.CheckHeadAdminAccess(context, requirement, refTuple.Item1);
116-
if (context.User.HasClaim(UserProfileClaimType.GameAdmin, refTuple.Item1.ToString()) &&
116+
if (BaseAuthorizationHelper.HasGameScopedClaim(context.User, UserProfileClaimType.GameAdmin, refTuple.Item1) &&
117117
BaseAuthorizationHelper.IsActionOwner(context, refTuple.Item2))
118118
context.Succeed(requirement);
119119
}
120120
else if (context.Resource is (GameType gameType, string adminId))
121121
{
122122
BaseAuthorizationHelper.CheckHeadAdminAccess(context, requirement, gameType);
123-
if (context.User.HasClaim(UserProfileClaimType.GameAdmin, gameType.ToString()) &&
123+
if (BaseAuthorizationHelper.HasGameScopedClaim(context.User, UserProfileClaimType.GameAdmin, gameType) &&
124124
BaseAuthorizationHelper.IsActionOwner(context, adminId))
125125
context.Succeed(requirement);
126126
}
@@ -157,10 +157,9 @@ AdminActionType.Warning or
157157

158158
private static void CheckActionSpecificEditPermissions(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType, AdminActionType adminActionType, string? adminId)
159159
{
160-
var gameTypeString = gameType.ToString();
161160
var isOwner = BaseAuthorizationHelper.IsActionOwner(context, adminId);
162-
var isModerator = context.User.HasClaim(UserProfileClaimType.Moderator, gameTypeString);
163-
var isGameAdmin = context.User.HasClaim(UserProfileClaimType.GameAdmin, gameTypeString);
161+
var isModerator = BaseAuthorizationHelper.HasGameScopedClaim(context.User, UserProfileClaimType.Moderator, gameType);
162+
var isGameAdmin = BaseAuthorizationHelper.HasGameScopedClaim(context.User, UserProfileClaimType.GameAdmin, gameType);
164163

165164
switch (adminActionType)
166165
{

src/XtremeIdiots.Portal.Web/Auth/Handlers/BaseAuthorizationHelper.cs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.AspNetCore.Authorization;
2+
using System.Security.Claims;
23
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
34
using XtremeIdiots.Portal.Web.Auth;
45
using XtremeIdiots.Portal.Web.Auth.Constants;
@@ -124,7 +125,7 @@ public static void CheckClaimTypes(AuthorizationHandlerContext context, IAuthori
124125
/// <param name="gameType">The game type to check permissions for</param>
125126
public static void CheckHeadAdminAccess(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType)
126127
{
127-
if (context.User.HasClaim(UserProfileClaimType.HeadAdmin, gameType.ToString()))
128+
if (HasGameScopedClaim(context.User, UserProfileClaimType.HeadAdmin, gameType))
128129
context.Succeed(requirement);
129130
}
130131

@@ -136,10 +137,8 @@ public static void CheckHeadAdminAccess(AuthorizationHandlerContext context, IAu
136137
/// <param name="gameType">The game type to check permissions for</param>
137138
public static void CheckGameAdminAccess(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType)
138139
{
139-
var gameTypeString = gameType.ToString();
140-
141-
if (context.User.HasClaim(UserProfileClaimType.HeadAdmin, gameTypeString) ||
142-
context.User.HasClaim(UserProfileClaimType.GameAdmin, gameTypeString))
140+
if (HasGameScopedClaim(context.User, UserProfileClaimType.HeadAdmin, gameType) ||
141+
HasGameScopedClaim(context.User, UserProfileClaimType.GameAdmin, gameType))
143142
{
144143
context.Succeed(requirement);
145144
}
@@ -153,10 +152,8 @@ public static void CheckGameAdminAccess(AuthorizationHandlerContext context, IAu
153152
/// <param name="gameType">The game type to check permissions for</param>
154153
public static void CheckModeratorAccess(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType)
155154
{
156-
var gameTypeString = gameType.ToString();
157-
158-
if (context.User.HasClaim(UserProfileClaimType.Moderator, gameTypeString) ||
159-
context.User.HasClaim(UserProfileClaimType.GameAdmin, gameTypeString))
155+
if (HasGameScopedClaim(context.User, UserProfileClaimType.Moderator, gameType) ||
156+
HasGameScopedClaim(context.User, UserProfileClaimType.GameAdmin, gameType))
160157
{
161158
context.Succeed(requirement);
162159
}
@@ -342,7 +339,7 @@ public static void CheckGameTypeAndServerAccess(AuthorizationHandlerContext cont
342339
/// <param name="gameType">The game type to check permissions for</param>
343340
public static void CheckGameServerAccess(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType)
344341
{
345-
if (context.User.HasClaim(AdditionalPermission.GameServers_Read, gameType.ToString()))
342+
if (HasGameScopedClaim(context.User, AdditionalPermission.GameServers_Read, gameType))
346343
context.Succeed(requirement);
347344
}
348345

@@ -392,7 +389,7 @@ public static void CheckCombinedGameServerAccess(AuthorizationHandlerContext con
392389
/// <param name="gameType">The game type to check permissions for</param>
393390
public static void CheckLiveRconAccess(AuthorizationHandlerContext context, IAuthorizationRequirement requirement, GameType gameType)
394391
{
395-
if (context.User.HasClaim(AdditionalPermission.GameServers_Admin_Rcon, gameType.ToString()))
392+
if (HasGameScopedClaim(context.User, AdditionalPermission.GameServers_Admin_Rcon, gameType))
396393
context.Succeed(requirement);
397394
}
398395

@@ -435,7 +432,7 @@ public static void CheckDirectPermissionGrant(AuthorizationHandlerContext contex
435432
if (resourceGameType.HasValue)
436433
{
437434
// Check game-scoped permission
438-
if (context.User.HasClaim(permissionClaimType, resourceGameType.Value.ToString()))
435+
if (HasGameScopedClaim(context.User, permissionClaimType, resourceGameType.Value))
439436
context.Succeed(requirement);
440437

441438
// Also check server-scoped permission if a server ID is present
@@ -451,6 +448,26 @@ public static void CheckDirectPermissionGrant(AuthorizationHandlerContext contex
451448
context.Succeed(requirement);
452449
}
453450

451+
/// <summary>
452+
/// Checks for a game-scoped claim, including known equivalent game mappings.
453+
/// </summary>
454+
public static bool HasGameScopedClaim(ClaimsPrincipal user, string claimType, GameType gameType)
455+
{
456+
return GetEquivalentGameTypes(gameType)
457+
.Any(equivalentGameType => user.HasClaim(claimType, equivalentGameType.ToString()));
458+
}
459+
460+
private static IReadOnlyList<GameType> GetEquivalentGameTypes(GameType gameType)
461+
{
462+
if (gameType == GameType.CallOfDuty4)
463+
return [GameType.CallOfDuty4, GameType.CallOfDuty4x];
464+
465+
if (gameType == GameType.CallOfDuty4x)
466+
return [GameType.CallOfDuty4x, GameType.CallOfDuty4];
467+
468+
return [gameType];
469+
}
470+
454471
#endregion
455472

456473
#region Utility Methods

0 commit comments

Comments
 (0)