fix(modtool): guard ticket lifecycle inputs

This commit is contained in:
simoleo89
2026-06-15 20:07:24 +02:00
parent 044d1141cd
commit 916ef7af3a
11 changed files with 172 additions and 10 deletions
@@ -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);
@@ -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);
@@ -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"));
@@ -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;
@@ -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<ModToolChatLog> chatlog = new ArrayList<>();
@@ -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())));
@@ -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) {
@@ -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;
@@ -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;
}
}
@@ -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));
}
}
@@ -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");
}
}