diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolCloseTicketEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolCloseTicketEvent.java index 98320092..591b1de9 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolCloseTicketEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolCloseTicketEvent.java @@ -15,9 +15,13 @@ public class ModToolCloseTicketEvent extends MessageHandler { this.packet.readInt(); int ticketId = this.packet.readInt(); + if (!ModToolTicketGuard.isPositiveId(ticketId) || state < 1 || state > 3) { + return; + } + ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(ticketId); - if (issue == null || issue.modId != this.client.getHabbo().getHabboInfo().getId()) + if (!ModToolTicketGuard.isOwnedBy(issue, this.client.getHabbo())) return; Habbo sender = Emulator.getGameEnvironment().getHabboManager().getHabbo(issue.senderId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueChangeTopicEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueChangeTopicEvent.java index 64655536..e2c69701 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueChangeTopicEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueChangeTopicEvent.java @@ -14,9 +14,17 @@ public class ModToolIssueChangeTopicEvent extends MessageHandler { this.packet.readInt(); int categoryId = this.packet.readInt(); + if (!ModToolTicketGuard.isPositiveId(ticketId) || !ModToolTicketGuard.isPositiveId(categoryId)) { + return; + } + + if (Emulator.getGameEnvironment().getModToolManager().getCfhTopic(categoryId) == null) { + return; + } + ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(ticketId); - if (issue != null) { + if (ModToolTicketGuard.isOwnedBy(issue, this.client.getHabbo())) { issue.category = categoryId; new UpdateModToolIssue(issue).run(); Emulator.getGameEnvironment().getModToolManager().updateTicketToMods(issue); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolPickTicketEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolPickTicketEvent.java index 172ad656..2bbd68ce 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolPickTicketEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolPickTicketEvent.java @@ -15,10 +15,17 @@ public class ModToolPickTicketEvent extends MessageHandler { public void handle() throws Exception { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { this.packet.readInt(); - ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(this.packet.readInt()); + int ticketId = this.packet.readInt(); + + if (!ModToolTicketGuard.isPositiveId(ticketId)) { + this.client.getHabbo().alert(Emulator.getTexts().getValue("support.ticket.picked.failed")); + return; + } + + ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(ticketId); if (issue != null) { - if (issue.state == ModToolTicketState.PICKED) { + if (!ModToolTicketGuard.canPick(issue)) { this.client.sendResponse(new ModToolIssueInfoComposer(issue)); this.client.getHabbo().alert(Emulator.getTexts().getValue("support.ticket.picked.failed")); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReleaseTicketEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReleaseTicketEvent.java index e0a155d9..cc08c125 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReleaseTicketEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolReleaseTicketEvent.java @@ -13,17 +13,22 @@ public class ModToolReleaseTicketEvent extends MessageHandler { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { int count = this.packet.readInt(); + if (!ModToolTicketGuard.isValidReleaseBatch(count)) { + return; + } + while (count != 0) { count--; int ticketId = this.packet.readInt(); + if (!ModToolTicketGuard.isPositiveId(ticketId)) { + continue; + } + ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(ticketId); - if (issue == null) - continue; - - if (issue.modId != this.client.getHabbo().getHabboInfo().getId()) + if (!ModToolTicketGuard.isOwnedBy(issue, this.client.getHabbo())) continue; issue.modId = 0; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestIssueChatlogEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestIssueChatlogEvent.java index 4eb2756e..0bc5df4d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestIssueChatlogEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestIssueChatlogEvent.java @@ -16,7 +16,13 @@ public class ModToolRequestIssueChatlogEvent extends MessageHandler { @Override public void handle() throws Exception { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { - ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(this.packet.readInt()); + int ticketId = this.packet.readInt(); + + if (!ModToolTicketGuard.isPositiveId(ticketId)) { + return; + } + + ModToolIssue issue = Emulator.getGameEnvironment().getModToolManager().getTicket(ticketId); if (issue != null) { List chatlog = new ArrayList<>(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomChatlogEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomChatlogEvent.java index 5f58f50e..2e2e3de1 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomChatlogEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomChatlogEvent.java @@ -12,7 +12,13 @@ public class ModToolRequestRoomChatlogEvent extends MessageHandler { public void handle() throws Exception { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { this.packet.readInt(); - Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.packet.readInt()); + int roomId = this.packet.readInt(); + + if (!ModToolTicketGuard.isPositiveId(roomId)) { + return; + } + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); if (room != null) this.client.sendResponse(new ModToolRoomChatlogComposer(room, Emulator.getGameEnvironment().getModToolManager().getRoomChatlog(room.getId()))); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomUserChatlogEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomUserChatlogEvent.java index 6fec0a90..a674ab4c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomUserChatlogEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestRoomUserChatlogEvent.java @@ -14,6 +14,10 @@ public class ModToolRequestRoomUserChatlogEvent extends MessageHandler { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { int userId = this.packet.readInt(); + if (!ModToolTicketGuard.isPositiveId(userId)) { + return; + } + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); if (habbo != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestUserChatlogEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestUserChatlogEvent.java index a175cdd0..f4ab8040 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestUserChatlogEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolRequestUserChatlogEvent.java @@ -13,6 +13,11 @@ public class ModToolRequestUserChatlogEvent extends MessageHandler { public void handle() throws Exception { if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) { int userId = this.packet.readInt(); + + if (!ModToolTicketGuard.isPositiveId(userId)) { + return; + } + HabboInfo habboInfo = HabboManager.getOfflineHabboInfo(userId); if (habboInfo == null) { return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuard.java new file mode 100644 index 00000000..a5844c6d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuard.java @@ -0,0 +1,28 @@ +package com.eu.habbo.messages.incoming.modtool; + +import com.eu.habbo.habbohotel.modtool.ModToolIssue; +import com.eu.habbo.habbohotel.modtool.ModToolTicketState; +import com.eu.habbo.habbohotel.users.Habbo; + +final class ModToolTicketGuard { + static final int MAX_RELEASE_BATCH = 50; + + private ModToolTicketGuard() { + } + + static boolean isPositiveId(int id) { + return id > 0; + } + + static boolean isValidReleaseBatch(int count) { + return count > 0 && count <= MAX_RELEASE_BATCH; + } + + static boolean isOwnedBy(ModToolIssue issue, Habbo moderator) { + return issue != null && moderator != null && issue.modId == moderator.getHabboInfo().getId(); + } + + static boolean canPick(ModToolIssue issue) { + return issue != null && issue.state != ModToolTicketState.PICKED; + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuardTest.java new file mode 100644 index 00000000..a6255d16 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketGuardTest.java @@ -0,0 +1,22 @@ +package com.eu.habbo.messages.incoming.modtool; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModToolTicketGuardTest { + @Test + void idsMustBePositive() { + assertFalse(ModToolTicketGuard.isPositiveId(0)); + assertFalse(ModToolTicketGuard.isPositiveId(-1)); + assertTrue(ModToolTicketGuard.isPositiveId(1)); + } + + @Test + void releaseBatchIsBounded() { + assertFalse(ModToolTicketGuard.isValidReleaseBatch(0)); + assertTrue(ModToolTicketGuard.isValidReleaseBatch(ModToolTicketGuard.MAX_RELEASE_BATCH)); + assertFalse(ModToolTicketGuard.isValidReleaseBatch(ModToolTicketGuard.MAX_RELEASE_BATCH + 1)); + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketLifecycleContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketLifecycleContractTest.java new file mode 100644 index 00000000..b74a7aee --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/modtool/ModToolTicketLifecycleContractTest.java @@ -0,0 +1,67 @@ +package com.eu.habbo.messages.incoming.modtool; + +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 ModToolTicketLifecycleContractTest { + @Test + void mutatingTicketActionsValidateOwnership() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); + + for (String handler : List.of( + "ModToolCloseTicketEvent.java", + "ModToolIssueChangeTopicEvent.java", + "ModToolReleaseTicketEvent.java" + )) { + String source = Files.readString(base.resolve(handler)); + + assertTrue(source.contains("ModToolTicketGuard.isOwnedBy(issue, this.client.getHabbo())"), + handler + " must only mutate tickets owned by the acting moderator"); + } + } + + @Test + void clientDrivenTicketAndChatlogIdsAreValidated() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); + + for (String handler : List.of( + "ModToolPickTicketEvent.java", + "ModToolCloseTicketEvent.java", + "ModToolIssueChangeTopicEvent.java", + "ModToolRequestIssueChatlogEvent.java", + "ModToolRequestRoomChatlogEvent.java", + "ModToolRequestRoomUserChatlogEvent.java", + "ModToolRequestUserChatlogEvent.java" + )) { + String source = Files.readString(base.resolve(handler)); + + assertTrue(source.contains("ModToolTicketGuard.isPositiveId"), + handler + " must reject zero or negative client-provided ids"); + } + } + + @Test + void releaseBatchAndCloseStateAreBounded() throws Exception { + Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool"); + String release = Files.readString(base.resolve("ModToolReleaseTicketEvent.java")); + String close = Files.readString(base.resolve("ModToolCloseTicketEvent.java")); + + assertTrue(release.contains("ModToolTicketGuard.isValidReleaseBatch(count)"), + "release ticket batches must be bounded before reading ticket ids"); + assertTrue(close.contains("state < 1 || state > 3"), + "close ticket must reject unknown close states before mutating the ticket"); + } + + @Test + void changeTopicRequiresKnownCategory() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/modtool/ModToolIssueChangeTopicEvent.java")); + + assertTrue(source.contains("getCfhTopic(categoryId) == null"), + "change-topic must reject unknown CFH categories before persisting"); + } +}