Skip to content

Commit e1af304

Browse files
committed
Prevent moderators from trolling admins and other moderators
1 parent 4aec38b commit e1af304

4 files changed

Lines changed: 69 additions & 25 deletions

File tree

Refresh.Core/Extensions/GameUserExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,17 @@ public static bool IsWriteBlocked(this GameUser user, GameServerConfig config)
1414

1515
return false;
1616
}
17+
18+
public static bool MayModifyUser(this GameUser user, GameUser targetUser)
19+
{
20+
// Users who are not at least a moderator may not update anyone else.
21+
if (user.Role < GameUserRole.Moderator)
22+
return false;
23+
24+
// Only admins may modify everyone, even other admins. Moderators may not modify other moderators and no admins either.
25+
if (user.Role < GameUserRole.Admin && targetUser.Role >= GameUserRole.Moderator)
26+
return false;
27+
28+
return true;
29+
}
1730
}

Refresh.Interfaces.APIv3/Endpoints/Admin/AdminUserApiEndpoints.cs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,25 @@ public ApiListResponse<ApiExtendedGameUserResponse> GetExtendedUsers(RequestCont
5353
[ApiV3Endpoint("admin/users/{idType}/{id}/resetPassword", HttpMethods.Put), MinimumRole(GameUserRole.Moderator)]
5454
[DocSummary("Resets a user's password by their UUID or username.")]
5555
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
56+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
5657
[DocRequestBody(typeof(ApiResetUserPasswordRequest))]
57-
public ApiOkResponse ResetUserPassword(RequestContext context, GameDatabaseContext database, ApiResetUserPasswordRequest body,
58+
public ApiOkResponse ResetUserPassword(RequestContext context, GameDatabaseContext database, ApiResetUserPasswordRequest body, GameUser user,
5859
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
5960
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
6061
{
61-
GameUser? user = database.GetUserByIdAndType(idType, id);
62-
if (user == null) return ApiNotFoundError.UserMissingError;
62+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
63+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
64+
65+
if (!user.MayModifyUser(targetUser))
66+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
6367

6468
if (body.PasswordSha512.Length != 128 || !CommonPatterns.Sha512Regex().IsMatch(body.PasswordSha512))
6569
return new ApiValidationError("Password is definitely not SHA512. Please hash the password.");
6670

6771
string? passwordBcrypt = BC.HashPassword(body.PasswordSha512, AuthenticationApiEndpoints.WorkFactor);
6872
if (passwordBcrypt == null) return new ApiInternalError("Could not BCrypt the given password.");
6973

70-
database.SetUserPassword(user, passwordBcrypt, true);
74+
database.SetUserPassword(targetUser, passwordBcrypt, true);
7175
return new ApiOkResponse();
7276
}
7377

@@ -98,28 +102,33 @@ public ApiResponse<ApiAdminUserPlanetsResponse> GetUserPlanets(RequestContext co
98102
[ApiV3Endpoint("admin/users/{idType}/{id}/planets", HttpMethods.Delete), MinimumRole(GameUserRole.Moderator)]
99103
[DocSummary("Resets a user's planets. Gets user by their UUID or username.")]
100104
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
101-
public ApiOkResponse ResetUserPlanets(RequestContext context, GameDatabaseContext database,
105+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
106+
public ApiOkResponse ResetUserPlanets(RequestContext context, GameDatabaseContext database, GameUser user,
102107
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
103108
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
104109
{
105-
GameUser? user = database.GetUserByIdAndType(idType, id);
106-
if (user == null) return ApiNotFoundError.UserMissingError;
110+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
111+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
107112

108-
database.ResetUserPlanets(user);
113+
if (!user.MayModifyUser(targetUser))
114+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
115+
116+
database.ResetUserPlanets(targetUser);
109117
return new ApiOkResponse();
110118
}
111119

112120
[ApiV3Endpoint("admin/users/{idType}/{id}", HttpMethods.Patch), MinimumRole(GameUserRole.Moderator)]
113121
[DocSummary("Updates the specified user's profile with the given data")]
114122
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
123+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
115124
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotOverwriteRoleErrorWhen)]
116125
[DocError(typeof(ApiValidationError), ApiValidationError.RoleMissingErrorWhen)]
117126
[DocError(typeof(ApiValidationError), ApiValidationError.WrongRoleUpdateMethodErrorWhen)]
118127
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.IconMissingErrorWhen)]
119128
[DocError(typeof(ApiValidationError), ApiValidationError.InvalidUsernameErrorWhen)]
120129
[DocError(typeof(ApiValidationError), ApiValidationError.UsernameTakenErrorWhen)]
121130
public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext context, GameDatabaseContext database,
122-
GameUser user, ApiAdminUpdateUserRequest body, DataContext dataContext,
131+
GameUser user, ApiAdminUpdateUserRequest body, DataContext dataContext,
123132
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
124133
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
125134
{
@@ -128,6 +137,9 @@ public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext contex
128137
if (targetUser == null)
129138
return ApiNotFoundError.UserMissingError;
130139

140+
if (!user.MayModifyUser(targetUser))
141+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
142+
131143
// Only admins may edit anyone's role.
132144
// TODO: Maybe moderators should also be able to set roles, but only for users below them, and to roles below them?
133145
if (body.Role != null)
@@ -196,14 +208,18 @@ public ApiResponse<ApiExtendedGameUserResponse> UpdateUser(RequestContext contex
196208
[ApiV3Endpoint("admin/users/{idType}/{id}", HttpMethods.Delete), MinimumRole(GameUserRole.Moderator)]
197209
[DocSummary("Deletes a user by their UUID or username.")]
198210
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
199-
public ApiOkResponse DeleteUser(RequestContext context, GameDatabaseContext database,
211+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
212+
public ApiOkResponse DeleteUser(RequestContext context, GameDatabaseContext database, GameUser user,
200213
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
201214
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
202215
{
203-
GameUser? user = database.GetUserByIdAndType(idType, id);
204-
if (user == null) return ApiNotFoundError.UserMissingError;
216+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
217+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
218+
219+
if (!user.MayModifyUser(targetUser))
220+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
205221

206-
database.DeleteUser(user);
222+
database.DeleteUser(targetUser);
207223
return new ApiOkResponse();
208224
}
209225
}

Refresh.Interfaces.APIv3/Endpoints/Admin/AdminUserPunishmentApiEndpoints.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,44 +17,56 @@ public class AdminUserPunishmentApiEndpoints : EndpointGroup
1717
[ApiV3Endpoint("admin/users/{idType}/{id}/ban", HttpMethods.Post), MinimumRole(GameUserRole.Moderator)]
1818
[DocSummary("Bans a user for the specified reason until the given date.")]
1919
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
20+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
2021
[DocRequestBody(typeof(ApiPunishUserRequest))]
21-
public ApiOkResponse BanUser(RequestContext context, GameDatabaseContext database, ApiPunishUserRequest body,
22+
public ApiOkResponse BanUser(RequestContext context, GameDatabaseContext database, ApiPunishUserRequest body, GameUser user,
2223
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
2324
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
2425
{
25-
GameUser? user = database.GetUserByIdAndType(idType, id);
26-
if (user == null) return ApiNotFoundError.UserMissingError;
26+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
27+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
2728

28-
database.BanUser(user, body.Reason, body.ExpiryDate);
29+
if (!user.MayModifyUser(targetUser))
30+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
31+
32+
database.BanUser(targetUser, body.Reason, body.ExpiryDate);
2933
return new ApiOkResponse();
3034
}
3135

3236
[ApiV3Endpoint("admin/users/{idType}/{id}/restrict", HttpMethods.Post), MinimumRole(GameUserRole.Moderator)]
3337
[DocSummary("Restricts a user for the specified reason until the given date.")]
3438
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
39+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
3540
[DocRequestBody(typeof(ApiPunishUserRequest))]
36-
public ApiOkResponse RestrictUser(RequestContext context, GameDatabaseContext database, ApiPunishUserRequest body,
41+
public ApiOkResponse RestrictUser(RequestContext context, GameDatabaseContext database, ApiPunishUserRequest body, GameUser user,
3742
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
3843
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
3944
{
40-
GameUser? user = database.GetUserByIdAndType(idType, id);
41-
if (user == null) return ApiNotFoundError.UserMissingError;
45+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
46+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
4247

43-
database.RestrictUser(user, body.Reason, body.ExpiryDate);
48+
if (!user.MayModifyUser(targetUser))
49+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
50+
51+
database.RestrictUser(targetUser, body.Reason, body.ExpiryDate);
4452
return new ApiOkResponse();
4553
}
4654

4755
[ApiV3Endpoint("admin/users/{idType}/{id}/pardon", HttpMethods.Post), MinimumRole(GameUserRole.Moderator)]
4856
[DocSummary("Pardons all punishments for the given user.")]
4957
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.UserMissingErrorWhen)]
50-
public ApiOkResponse PardonUser(RequestContext context, GameDatabaseContext database,
58+
[DocError(typeof(ApiValidationError), ApiValidationError.MayNotModifyUserDueToLowRoleErrorWhen)]
59+
public ApiOkResponse PardonUser(RequestContext context, GameDatabaseContext database, GameUser user,
5160
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
5261
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
5362
{
54-
GameUser? user = database.GetUserByIdAndType(idType, id);
55-
if (user == null) return ApiNotFoundError.UserMissingError;
63+
GameUser? targetUser = database.GetUserByIdAndType(idType, id);
64+
if (targetUser == null) return ApiNotFoundError.UserMissingError;
65+
66+
if (!user.MayModifyUser(targetUser))
67+
return ApiValidationError.MayNotModifyUserDueToLowRoleError;
5668

57-
database.SetUserRole(user, GameUserRole.User);
69+
database.SetUserRole(targetUser, GameUserRole.User);
5870
return new ApiOkResponse();
5971
}
6072
}

Refresh.Interfaces.APIv3/Endpoints/ApiTypes/Errors/ApiValidationError.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public class ApiValidationError : ApiError
6565
public const string MayNotOverwriteRoleErrorWhen = "You may not overwrite user roles because you are not an admin";
6666
public static readonly ApiValidationError MayNotOverwriteRoleError = new(MayNotOverwriteRoleErrorWhen);
6767

68+
public const string MayNotModifyUserDueToLowRoleErrorWhen = "You may not modify this user because their role is above yours, or the same as yours incase you're not an admin.";
69+
public static readonly ApiValidationError MayNotModifyUserDueToLowRoleError = new(MayNotModifyUserDueToLowRoleErrorWhen);
70+
6871
public const string WrongRoleUpdateMethodErrorWhen = "The specified role cannot be assigned to the user using this endpoint.";
6972
public static readonly ApiValidationError WrongRoleUpdateMethodError = new(WrongRoleUpdateMethodErrorWhen);
7073

0 commit comments

Comments
 (0)