From ec24283e0ff39416b2d904cfcd7919adb3a08270 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 14 Jun 2026 22:17:47 +0200 Subject: [PATCH] fix(housekeeping): protect room owner mutations --- .../HousekeepingDeleteRoomEvent.java | 18 +++++-- .../HousekeepingKickAllFromRoomEvent.java | 5 ++ .../HousekeepingMuteRoomEvent.java | 7 ++- .../housekeeping/HousekeepingRoomGuard.java | 13 +++++ .../HousekeepingRoomStateEvent.java | 5 ++ ...ousekeepingTransferRoomOwnershipEvent.java | 17 +++++++ .../HousekeepingRoomGuardContractTest.java | 47 +++++++++++++++++++ 7 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuardContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java index 52702f23..9de0abb8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java @@ -42,11 +42,14 @@ public class HousekeepingDeleteRoomEvent extends MessageHandler { Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); - if (room != null) { - room.ejectAll(); - room.preventUnloading = false; - room.dispose(); - Emulator.getGameEnvironment().getRoomManager().uncacheRoom(room); + if (room == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + + if (!HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; } try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); @@ -63,6 +66,11 @@ public class HousekeepingDeleteRoomEvent extends MessageHandler { return; } + room.ejectAll(); + room.preventUnloading = false; + room.dispose(); + Emulator.getGameEnvironment().getRoomManager().uncacheRoom(room); + com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log( this.client.getHabbo().getHabboInfo().getId(), this.client.getHabbo().getHabboInfo().getUsername(), diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java index 72644201..20867ab0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java @@ -34,6 +34,11 @@ public class HousekeepingKickAllFromRoomEvent extends MessageHandler { return; } + if (!HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + room.ejectAll(); com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log( diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java index 1d79f96f..f707e1f6 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java @@ -31,7 +31,7 @@ public class HousekeepingMuteRoomEvent extends MessageHandler { int roomId = this.packet.readInt(); int minutes = this.packet.readInt(); - if (roomId <= 0) { + if (roomId <= 0 || minutes < 0) { this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); return; } @@ -43,6 +43,11 @@ public class HousekeepingMuteRoomEvent extends MessageHandler { return; } + if (!HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + room.setMuted(minutes > 0); com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log( diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuard.java new file mode 100644 index 00000000..ec604b31 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuard.java @@ -0,0 +1,13 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.Habbo; + +final class HousekeepingRoomGuard { + private HousekeepingRoomGuard() { + } + + static boolean canManageRoom(Habbo operator, Room room) { + return room != null && HousekeepingTargetRankGuard.canTargetUser(operator, room.getOwnerId()); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java index 3cf5f984..d7eb5645 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java @@ -41,6 +41,11 @@ public class HousekeepingRoomStateEvent extends MessageHandler { return; } + if (!HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room)) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.rank_too_high")); + return; + } + room.setState(open ? RoomState.OPEN : RoomState.LOCKED); room.save(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java index eba00fb2..b038d347 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.housekeeping; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; @@ -39,6 +40,19 @@ public class HousekeepingTransferRoomOwnershipEvent extends MessageHandler { return; } + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + if (room == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + + if (!HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room) || + !HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), newOwnerId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + HabboInfo newOwner = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(newOwnerId); if (newOwner == null) { @@ -62,6 +76,9 @@ public class HousekeepingTransferRoomOwnershipEvent extends MessageHandler { return; } + room.setOwnerId(newOwnerId); + room.setOwnerName(newOwner.getUsername()); + com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log( this.client.getHabbo().getHabboInfo().getId(), this.client.getHabbo().getHabboInfo().getUsername(), diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuardContractTest.java new file mode 100644 index 00000000..d29e41af --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuardContractTest.java @@ -0,0 +1,47 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HousekeepingRoomGuardContractTest { + @Test + void destructiveRoomActionsRespectOwnerRankCeiling() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping"); + + for (String handler : List.of( + "HousekeepingDeleteRoomEvent.java", + "HousekeepingKickAllFromRoomEvent.java", + "HousekeepingMuteRoomEvent.java", + "HousekeepingRoomStateEvent.java", + "HousekeepingTransferRoomOwnershipEvent.java" + )) { + String source = Files.readString(base.resolve(handler)); + + assertTrue(source.contains("HousekeepingRoomGuard.canManageRoom(this.client.getHabbo(), room)"), + handler + " must reject room mutations when the room owner is peer-or-higher ranked"); + assertTrue(source.contains("housekeeping.error.rank_too_high"), + handler + " must surface a rank-ceiling error for protected room owners"); + } + } + + @Test + void roomGuardDelegatesToTargetRankGuard() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomGuard.java")); + + assertTrue(source.contains("HousekeepingTargetRankGuard.canTargetUser(operator, room.getOwnerId())"), + "room-owner checks must use the same core-rank peer override as user moderation"); + } + + @Test + void roomMuteRejectsNegativeDurations() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java")); + + assertTrue(source.contains("minutes < 0"), + "room mute should reject negative duration values instead of treating them as unmute"); + } +}