fix(housekeeping): enforce rank ceilings on rank changes

This commit is contained in:
simoleo89
2026-06-14 21:55:19 +02:00
parent d9cf70910f
commit 31027095ec
3 changed files with 35 additions and 9 deletions
@@ -45,13 +45,7 @@ public class HousekeepingSetUserRankEvent extends MessageHandler {
Rank rank = permissions.getRank(rankId);
// Rank-ceiling guard: an operator must never be able to grant a rank
// above their own, nor modify a user who already outranks them. This
// mirrors GiveRankCommand and prevents privilege escalation through
// the housekeeping path (including self-promotion).
int operatorRankId = this.client.getHabbo().getHabboInfo().getRank().getId();
if (rank.getId() > operatorRankId) {
if (!HousekeepingTargetRankGuard.canAssignRank(this.client.getHabbo(), rank.getId())) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high"));
return;
}
@@ -77,7 +71,12 @@ public class HousekeepingSetUserRankEvent extends MessageHandler {
}
}
if (targetRankId > operatorRankId) {
if (targetRankId <= 0) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
return;
}
if (!HousekeepingTargetRankGuard.canTargetRank(this.client.getHabbo(), targetRankId)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high"));
return;
}
@@ -19,12 +19,23 @@ final class HousekeepingTargetRankGuard {
return true;
}
return canTargetRank(operator, targetInfo.getRank().getId());
}
static boolean canTargetRank(Habbo operator, int targetRankId) {
if (operator == null || targetRankId <= 0) {
return false;
}
int operatorRankId = operator.getHabboInfo().getRank().getId();
int targetRankId = targetInfo.getRank().getId();
return targetRankId < operatorRankId || isCoreRank(operatorRankId) && targetRankId <= operatorRankId;
}
static boolean canAssignRank(Habbo operator, int rankId) {
return canTargetRank(operator, rankId);
}
private static boolean isCoreRank(int rankId) {
int highestRankId = 0;
for (Rank rank : Emulator.getGameEnvironment().getPermissionsManager().getAllRanks()) {
@@ -27,6 +27,10 @@ class HousekeepingTargetRankGuardContractTest {
void privilegedUserActionsRejectPeerRanksUnlessOperatorIsCoreRank() throws Exception {
String guard = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuard.java"));
assertTrue(guard.contains("static boolean canTargetRank(Habbo operator, int targetRankId)"),
"rank comparison should be reusable for online and offline housekeeping targets");
assertTrue(guard.contains("static boolean canAssignRank(Habbo operator, int rankId)"),
"rank assignment should use the same peer/core ceiling as target moderation");
assertTrue(guard.contains("targetRankId < operatorRankId"),
"non-core housekeeping operators must only target lower-ranked users");
assertTrue(guard.contains("isCoreRank(operatorRankId) && targetRankId <= operatorRankId"),
@@ -47,4 +51,16 @@ class HousekeepingTargetRankGuardContractTest {
handler + " must return a rank-ceiling error when the target cannot be managed");
}
}
@Test
void housekeepingRankChangesUseCentralRankCeilings() throws Exception {
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetUserRankEvent.java"));
assertTrue(source.contains("HousekeepingTargetRankGuard.canAssignRank(this.client.getHabbo(), rank.getId())"),
"housekeeping rank assignment must not grant peer-or-higher ranks to non-core operators");
assertTrue(source.contains("HousekeepingTargetRankGuard.canTargetRank(this.client.getHabbo(), targetRankId)"),
"housekeeping rank assignment must not modify peer-or-higher ranked targets for non-core operators");
assertTrue(source.contains("housekeeping.error.user_not_found"),
"rank changes must reject missing offline users instead of reporting success for a zero-row update");
}
}