From 36a06647f0dc3e3fe7f920a90594b617ea59c49e Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 15 Jun 2026 19:51:36 +0200 Subject: [PATCH] fix(modtool): enforce staff target rank ceilings --- .../habbohotel/modtool/ModToolManager.java | 40 ++++++++++++++----- .../habbohotel/modtool/ModToolSanctions.java | 4 ++ .../ModToolIssueDefaultSanctionEvent.java | 4 ++ .../ModToolPermissionContractTest.java | 28 ++++++++++++- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java index 9883ec7a..efa38b8b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java @@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.modtool; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.permissions.Rank; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomState; import com.eu.habbo.habbohotel.users.Habbo; @@ -423,12 +424,16 @@ public class ModToolManager { } public void kick(Habbo moderator, Habbo target, String message) { - if (moderator.hasPermission(Permission.ACC_SUPPORTTOOL) && !target.hasPermission(Permission.ACC_UNKICKABLE)) { - if (target.getHabboInfo().getCurrentRoom() != null) { - Emulator.getGameEnvironment().getRoomManager().leaveRoom(target, target.getHabboInfo().getCurrentRoom()); - } - this.alert(moderator, target, message, SupportUserAlertedReason.KICKED); + if (moderator == null || target == null || !moderator.hasPermission(Permission.ACC_SUPPORTTOOL) || + target.hasPermission(Permission.ACC_UNKICKABLE) || + !canModerateTarget(moderator, target.getHabboInfo().getId())) { + return; } + + if (target.getHabboInfo().getCurrentRoom() != null) { + Emulator.getGameEnvironment().getRoomManager().leaveRoom(target, target.getHabboInfo().getCurrentRoom()); + } + this.alert(moderator, target, message, SupportUserAlertedReason.KICKED); } public List ban(int targetUserId, Habbo moderator, String reason, int duration, ModToolBanType type, int cfhTopic) { @@ -443,7 +448,7 @@ public class ModToolManager { return bans; } - if (moderator.getHabboInfo().getRank().getId() < offlineInfo.getRank().getId()) { + if (!canModerateTarget(moderator, targetUserId)) { return bans; } @@ -468,7 +473,7 @@ public class ModToolManager { if ((type == ModToolBanType.IP || type == ModToolBanType.SUPER) && target != null && !ban.ip.equals("offline")) { for (Habbo h : Emulator.getGameServer().getGameClientManager().getHabbosWithIP(ban.ip)) { - if (h.getHabboInfo().getRank().getId() >= moderator.getHabboInfo().getRank().getId()) continue; + if (!canModerateTarget(moderator, h.getHabboInfo().getId())) continue; ban = new ModToolBan(h.getHabboInfo().getId(), h != null ? h.getHabboInfo().getIpLogin() : "offline", h != null ? h.getClient().getMachineId() : "offline", moderator.getHabboInfo().getId(), Emulator.getIntUnixTimestamp() + duration, reason, type, cfhTopic); Emulator.getPluginManager().fireEvent(new SupportUserBannedEvent(moderator, h, ban)); @@ -480,7 +485,7 @@ public class ModToolManager { if ((type == ModToolBanType.MACHINE || type == ModToolBanType.SUPER) && target != null && !ban.machineId.equals("offline")) { for (Habbo h : Emulator.getGameServer().getGameClientManager().getHabbosWithMachineId(ban.machineId)) { - if (h.getHabboInfo().getRank().getId() >= moderator.getHabboInfo().getRank().getId()) continue; + if (!canModerateTarget(moderator, h.getHabboInfo().getId())) continue; ban = new ModToolBan(h.getHabboInfo().getId(), h != null ? h.getHabboInfo().getIpLogin() : "offline", h != null ? h.getClient().getMachineId() : "offline", moderator.getHabboInfo().getId(), Emulator.getIntUnixTimestamp() + duration, reason, type, cfhTopic); Emulator.getPluginManager().fireEvent(new SupportUserBannedEvent(moderator, h, ban)); @@ -501,10 +506,27 @@ public class ModToolManager { if (targetInfo == null) return false; - return targetInfo.getRank().getId() < moderator.getHabboInfo().getRank().getId(); + int moderatorRankId = moderator.getHabboInfo().getRank().getId(); + int targetRankId = targetInfo.getRank().getId(); + + return targetRankId < moderatorRankId || isCoreRank(moderatorRankId) && targetRankId <= moderatorRankId; + } + + private static boolean isCoreRank(int rankId) { + int highestRankId = 0; + for (Rank rank : Emulator.getGameEnvironment().getPermissionsManager().getAllRanks()) { + highestRankId = Math.max(highestRankId, rank.getId()); + } + + return highestRankId > 0 && rankId >= highestRankId; } public void roomAction(Room room, Habbo moderator, boolean kickUsers, boolean lockDoor, boolean changeTitle) { + if (room == null || moderator == null || !moderator.hasPermission(Permission.ACC_SUPPORTTOOL) || + !canModerateTarget(moderator, room.getOwnerId())) { + return; + } + SupportRoomActionEvent roomActionEvent = new SupportRoomActionEvent(moderator, room, kickUsers, lockDoor, changeTitle); Emulator.getPluginManager().fireEvent(roomActionEvent); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolSanctions.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolSanctions.java index 14b2dfeb..85275631 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolSanctions.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolSanctions.java @@ -128,6 +128,10 @@ public class ModToolSanctions { } public void run(int habboId, Habbo self, int sanctionLevel, int cfhTopic, String reason, int tradeLockedUntil, boolean isMuted, int muteDuration) { + if (!ModToolManager.canModerateTarget(self, habboId)) { + return; + } + sanctionLevel++; ModToolSanctionLevelItem sanctionLevelItem = getSanctionLevelItem(sanctionLevel); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueDefaultSanctionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueDefaultSanctionEvent.java index b237c5f5..2b0789f6 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueDefaultSanctionEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueDefaultSanctionEvent.java @@ -21,6 +21,10 @@ public class ModToolIssueDefaultSanctionEvent extends MessageHandler { ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(issueId); + if (issue == null) { + return; + } + if (issue.modId == this.client.getHabbo().getHabboInfo().getId()) { CfhTopic modToolCategory = Emulator.getGameEnvironment().getModToolManager().getCfhTopic(category); diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolPermissionContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolPermissionContractTest.java index 460da889..9ab0ff8d 100644 --- a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolPermissionContractTest.java +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolPermissionContractTest.java @@ -45,7 +45,7 @@ class ModToolPermissionContractTest { } @Test - void modToolSanctionsCannotTargetSameOrHigherRanks() throws Exception { + void modToolSanctionsCannotTargetPeerRanksUnlessOperatorIsCoreRank() throws Exception { Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); for (String handler : List.of( @@ -60,6 +60,30 @@ class ModToolPermissionContractTest { String manager = Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java")); assertTrue(manager.contains("!canModerateTarget(moderator, target.getHabboInfo().getId())"), - "ModToolManager.alert must refuse alerts/warnings against same-or-higher-rank targets"); + "ModToolManager.alert must refuse alerts/warnings against protected targets"); + assertTrue(manager.contains("targetRankId < moderatorRankId"), + "non-core moderators must only target lower-ranked users"); + assertTrue(manager.contains("isCoreRank(moderatorRankId) && targetRankId <= moderatorRankId"), + "highest/core moderators should be allowed to target peer ranks"); + assertTrue(manager.contains("private static boolean isCoreRank(int rankId)"), + "core-rank detection should be centralized in ModToolManager"); + } + + @Test + void managerEntryPointsShareTargetAndRoomOwnerGuards() throws Exception { + String manager = Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java")); + String sanctions = Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/modtool/ModToolSanctions.java")); + String defaultSanction = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueDefaultSanctionEvent.java")); + + assertTrue(manager.contains("!canModerateTarget(moderator, targetUserId)"), + "ModToolManager.ban must use the central target-rank guard for offline and online users"); + assertTrue(manager.contains("!canModerateTarget(moderator, h.getHabboInfo().getId())"), + "IP and machine fan-out bans must skip protected peer-or-higher ranked sessions"); + assertTrue(manager.contains("!canModerateTarget(moderator, room.getOwnerId())"), + "ModToolManager.roomAction must refuse mutations on rooms owned by protected ranks"); + assertTrue(sanctions.contains("!ModToolManager.canModerateTarget(self, habboId)"), + "ModToolSanctions.run must guard every sanction path before writing or applying it"); + assertTrue(defaultSanction.contains("if (issue == null)"), + "default sanctions must tolerate stale or missing ticket ids"); } }