diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java index 4b2c1619..1b637fed 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingBanUserEvent.java @@ -41,6 +41,11 @@ public class HousekeepingBanUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + long durationLong = (long) hours * SECONDS_IN_HOUR; int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingForceDisconnectUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingForceDisconnectUserEvent.java index 8e642ffa..9ab0338c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingForceDisconnectUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingForceDisconnectUserEvent.java @@ -40,6 +40,11 @@ public class HousekeepingForceDisconnectUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + if (reason != null && !reason.isEmpty()) { target.alert(reason); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java index c47bad24..9b949df7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java @@ -33,6 +33,11 @@ public class HousekeepingGiveCreditsEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if (online != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java index 5e5053a1..f141c4ce 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java @@ -42,6 +42,11 @@ public class HousekeepingGiveCurrencyEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.rank_too_high")); + return; + } + Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if (online != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java index cf1ecb6b..15ece6ad 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java @@ -40,6 +40,11 @@ public class HousekeepingGrantItemEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + if (quantity > MAX_QUANTITY_PER_CALL) { quantity = MAX_QUANTITY_PER_CALL; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickUserEvent.java index 57bc8341..7f2006cb 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickUserEvent.java @@ -42,6 +42,11 @@ public class HousekeepingKickUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + if (target.hasPermission(Permission.ACC_UNKICKABLE)) { this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.target_unkickable")); return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteUserEvent.java index 43f12eb1..4b35c838 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteUserEvent.java @@ -44,6 +44,11 @@ public class HousekeepingMuteUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + target.mute(minutes * SECONDS_IN_MINUTE, false); if (reason != null && !reason.isEmpty()) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingResetUserPasswordEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingResetUserPasswordEvent.java index 14142002..87fe3e5a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingResetUserPasswordEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingResetUserPasswordEvent.java @@ -46,6 +46,11 @@ public class HousekeepingResetUserPasswordEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + String plain = randomPassword(); String hash; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java index 9facf4f4..508c2a7c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java @@ -38,6 +38,11 @@ public class HousekeepingSetHcSubscriptionEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + int now = Emulator.getIntUnixTimestamp(); int newExpire; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuard.java new file mode 100644 index 00000000..ee0e4965 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuard.java @@ -0,0 +1,23 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboInfo; + +final class HousekeepingTargetRankGuard { + private HousekeepingTargetRankGuard() { + } + + static boolean canTargetUser(Habbo operator, int targetUserId) { + if (operator == null || targetUserId <= 0) { + return false; + } + + HabboInfo targetInfo = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(targetUserId); + if (targetInfo == null) { + return true; + } + + return targetInfo.getRank().getId() < operator.getHabboInfo().getRank().getId(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTradeLockUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTradeLockUserEvent.java index cf961c15..2c0a5669 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTradeLockUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTradeLockUserEvent.java @@ -43,6 +43,11 @@ public class HousekeepingTradeLockUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + long durationLong = (long) hours * SECONDS_IN_HOUR; int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong; int lockedUntil = Emulator.getIntUnixTimestamp() + duration; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingUnbanUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingUnbanUserEvent.java index 74ed38e4..548351fa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingUnbanUserEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingUnbanUserEvent.java @@ -34,6 +34,11 @@ public class HousekeepingUnbanUserEvent extends MessageHandler { return; } + if (!HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high")); + return; + } + // ModToolManager.unban only takes a username; the SQL UPDATE // happens against active bans (ban_expire > now), so calling it // on a never-banned user is a benign no-op that returns false. diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuardContractTest.java new file mode 100644 index 00000000..ed127abd --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuardContractTest.java @@ -0,0 +1,46 @@ +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 HousekeepingTargetRankGuardContractTest { + private static final List RANK_GUARDED_HANDLERS = List.of( + "HousekeepingBanUserEvent.java", + "HousekeepingForceDisconnectUserEvent.java", + "HousekeepingGiveCreditsEvent.java", + "HousekeepingGiveCurrencyEvent.java", + "HousekeepingGrantItemEvent.java", + "HousekeepingKickUserEvent.java", + "HousekeepingMuteUserEvent.java", + "HousekeepingResetUserPasswordEvent.java", + "HousekeepingSetHcSubscriptionEvent.java", + "HousekeepingTradeLockUserEvent.java", + "HousekeepingUnbanUserEvent.java" + ); + + @Test + void privilegedUserActionsRejectPeerAndHigherRankTargets() throws Exception { + String guard = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTargetRankGuard.java")); + + assertTrue(guard.contains("targetInfo.getRank().getId() < operator.getHabboInfo().getRank().getId()"), + "Housekeeping user actions must only target lower-ranked users"); + } + + @Test + void sensitiveHousekeepingUserActionsUseRankGuard() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping"); + + for (String handler : RANK_GUARDED_HANDLERS) { + String source = Files.readString(base.resolve(handler)); + assertTrue(source.contains("HousekeepingTargetRankGuard.canTargetUser(this.client.getHabbo(), userId)"), + handler + " must reject equal or higher-ranked targets before applying privileged user actions"); + assertTrue(source.contains("housekeeping.error.rank_too_high"), + handler + " must return a rank-ceiling error when the target cannot be managed"); + } + } +}