Merge pull request #213 from simoleo89/fix/room-user-safety

Guard room user moderation packets
This commit is contained in:
DuckieTM
2026-06-15 22:18:09 +02:00
committed by GitHub
4 changed files with 99 additions and 6 deletions
@@ -9,6 +9,9 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.users.MutedWhisperComposer;
public class RoomUserMuteEvent extends MessageHandler {
private static final int MIN_MUTE_MINUTES = 1;
private static final int MAX_MUTE_MINUTES = 1440;
@Override
public void handle() throws Exception {
int userId = this.packet.readInt();
@@ -24,6 +27,12 @@ public class RoomUserMuteEvent extends MessageHandler {
Habbo habbo = room.getHabbo(userId);
if (habbo != null) {
if (minutes < MIN_MUTE_MINUTES || minutes > MAX_MUTE_MINUTES)
return;
if (habbo.hasPermission(Permission.ACC_UNKICKABLE))
return;
room.muteHabbo(habbo, minutes);
habbo.getClient().sendResponse(new MutedWhisperComposer(minutes * 60));
AchievementManager.progressAchievement(this.client.getHabbo(), Emulator.getGameEnvironment().getAchievementManager().getAchievement("SelfModMuteSeen"));
@@ -11,10 +11,16 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.plugin.events.users.UserSignEvent;
public class RoomUserSignEvent extends MessageHandler {
private static final int MIN_SIGN_ID = 0;
private static final int MAX_SIGN_ID = 10;
@Override
public void handle() throws Exception {
int signId = this.packet.readInt();
if (signId < MIN_SIGN_ID || signId > MAX_SIGN_ID)
return;
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room == null)
@@ -26,12 +32,10 @@ public class RoomUserSignEvent extends MessageHandler {
this.client.getHabbo().getHabboInfo().getCurrentRoom().unIdle(this.client.getHabbo());
WiredManager.triggerUserPerformsAction(room, this.client.getHabbo().getRoomUnit(), WiredUserActionType.SIGN, event.sign);
if(signId <= 10) {
int userId = this.client.getHabbo().getHabboInfo().getId();
for (HabboItem item : room.getRoomSpecialTypes().getItemsOfType(InteractionVoteCounter.class)) {
if (item instanceof InteractionVoteCounter) {
((InteractionVoteCounter)item).vote(room, userId, signId);
}
int userId = this.client.getHabbo().getHabboInfo().getId();
for (HabboItem item : room.getRoomSpecialTypes().getItemsOfType(InteractionVoteCounter.class)) {
if (item instanceof InteractionVoteCounter) {
((InteractionVoteCounter)item).vote(room, userId, signId);
}
}
}
@@ -0,0 +1,39 @@
package com.eu.habbo.messages.incoming.rooms.users;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RoomUserMuteGuardContractTest {
private static String source() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserMuteEvent.java"));
}
@Test
void muteDurationIsBoundedBeforeApplyingMute() throws Exception {
String source = source();
int targetLookup = source.indexOf("Habbo habbo = room.getHabbo(userId)");
int durationGuard = source.indexOf("minutes < MIN_MUTE_MINUTES || minutes > MAX_MUTE_MINUTES", targetLookup);
int muteCall = source.indexOf("room.muteHabbo(habbo, minutes)", targetLookup);
assertTrue(targetLookup > -1, "Mute handler must resolve the room target");
assertTrue(durationGuard > targetLookup, "Mute handler must bound client-provided minutes");
assertTrue(durationGuard < muteCall, "Mute duration must be validated before mutating room state");
}
@Test
void unkickableTargetsCannotBeMutedThroughRoomPacket() throws Exception {
String source = source();
int unkickableGuard = source.indexOf("habbo.hasPermission(Permission.ACC_UNKICKABLE)");
int muteCall = source.indexOf("room.muteHabbo(habbo, minutes)");
assertTrue(unkickableGuard > -1, "Room mute must respect ACC_UNKICKABLE like kick and ban");
assertTrue(unkickableGuard < muteCall, "Unkickable targets must be rejected before muting");
}
}
@@ -0,0 +1,41 @@
package com.eu.habbo.messages.incoming.rooms.users;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RoomUserSignGuardContractTest {
private static String source() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java"));
}
@Test
void signIdIsValidatedBeforeRoomStateAndWiredTriggers() throws Exception {
String source = source();
int signRead = source.indexOf("int signId = this.packet.readInt()");
int guard = source.indexOf("signId < MIN_SIGN_ID || signId > MAX_SIGN_ID", signRead);
int status = source.indexOf("setStatus(RoomUnitStatus.SIGN", signRead);
int wired = source.indexOf("WiredManager.triggerUserPerformsAction", signRead);
assertTrue(signRead > -1, "Sign handler must read the client-provided sign id");
assertTrue(guard > signRead, "Sign handler must reject out-of-range sign ids");
assertTrue(guard < status, "Sign id must be validated before status mutation");
assertTrue(guard < wired, "Sign id must be validated before wired triggers");
}
@Test
void voteCountersOnlyReceiveValidatedSigns() throws Exception {
String source = source();
int guard = source.indexOf("signId < MIN_SIGN_ID || signId > MAX_SIGN_ID");
int vote = source.indexOf(".vote(room, userId, signId)");
assertTrue(guard > -1, "Sign id range guard must exist");
assertTrue(vote > guard, "Vote counters must only receive signs after the range guard");
}
}