You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 15:36:17 +00:00
fix(rcon): bound mute and achievement mutations
This commit is contained in:
@@ -157,6 +157,8 @@ public final class Emulator {
|
|||||||
Emulator.config.register("rcon.rate_limit.limit_for_period", "60");
|
Emulator.config.register("rcon.rate_limit.limit_for_period", "60");
|
||||||
Emulator.config.register("rcon.rate_limit.refresh_period_ms", "1000");
|
Emulator.config.register("rcon.rate_limit.refresh_period_ms", "1000");
|
||||||
Emulator.config.register("rcon.rate_limit.timeout_ms", "0");
|
Emulator.config.register("rcon.rate_limit.timeout_ms", "0");
|
||||||
|
Emulator.config.register("rcon.mute.max_duration_seconds", "604800");
|
||||||
|
Emulator.config.register("rcon.achievement.max_progress", "10000");
|
||||||
String hotelTimezoneId = Emulator.getConfig().getValue("hotel.timezone", java.time.ZoneId.systemDefault().getId());
|
String hotelTimezoneId = Emulator.getConfig().getValue("hotel.timezone", java.time.ZoneId.systemDefault().getId());
|
||||||
System.out.println();
|
System.out.println();
|
||||||
LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, ");
|
LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, ");
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.eu.habbo.messages.rcon;
|
|||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@ import java.sql.SQLException;
|
|||||||
|
|
||||||
public class MuteUser extends RCONMessage<MuteUser.JSON> {
|
public class MuteUser extends RCONMessage<MuteUser.JSON> {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MuteUser.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(MuteUser.class);
|
||||||
|
static final int DEFAULT_MAX_DURATION_SECONDS = 604_800;
|
||||||
|
|
||||||
public MuteUser() {
|
public MuteUser() {
|
||||||
super(MuteUser.JSON.class);
|
super(MuteUser.JSON.class);
|
||||||
@@ -19,6 +22,13 @@ public class MuteUser extends RCONMessage<MuteUser.JSON> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(Gson gson, JSON json) {
|
public void handle(Gson gson, JSON json) {
|
||||||
|
int maxDuration = parseMaxDuration(Emulator.getConfig().getValue("rcon.mute.max_duration_seconds", String.valueOf(DEFAULT_MAX_DURATION_SECONDS)));
|
||||||
|
if (json.duration < 0 || json.duration > maxDuration) {
|
||||||
|
this.status = RCONMessage.STATUS_ERROR;
|
||||||
|
this.message = "duration must be between 0 and " + maxDuration + " seconds";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
|
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
|
||||||
|
|
||||||
if (habbo != null) {
|
if (habbo != null) {
|
||||||
@@ -29,7 +39,7 @@ public class MuteUser extends RCONMessage<MuteUser.JSON> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET mute_end_timestamp = ? WHERE user_id = ? LIMIT 1")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET mute_end_timestamp = ? WHERE user_id = ? LIMIT 1")) {
|
||||||
statement.setInt(1, Emulator.getIntUnixTimestamp() + json.duration);
|
statement.setInt(1, json.duration == 0 ? 0 : Emulator.getIntUnixTimestamp() + json.duration);
|
||||||
statement.setInt(2, json.user_id);
|
statement.setInt(2, json.user_id);
|
||||||
if (statement.executeUpdate() == 0) {
|
if (statement.executeUpdate() == 0) {
|
||||||
this.status = HABBO_NOT_FOUND;
|
this.status = HABBO_NOT_FOUND;
|
||||||
@@ -40,11 +50,24 @@ public class MuteUser extends RCONMessage<MuteUser.JSON> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parseMaxDuration(String configured) {
|
||||||
|
try {
|
||||||
|
int parsed = Integer.parseInt(configured);
|
||||||
|
if (parsed >= 0) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_MAX_DURATION_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
static class JSON {
|
static class JSON {
|
||||||
|
|
||||||
|
@Positive(message = "invalid user")
|
||||||
public int user_id;
|
public int user_id;
|
||||||
|
|
||||||
|
@Min(value = 0, message = "invalid duration")
|
||||||
public int duration;
|
public int duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import com.eu.habbo.habbohotel.achievements.Achievement;
|
|||||||
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
|
|
||||||
public class ProgressAchievement extends RCONMessage<ProgressAchievement.ProgressAchievementJSON> {
|
public class ProgressAchievement extends RCONMessage<ProgressAchievement.ProgressAchievementJSON> {
|
||||||
|
static final int DEFAULT_MAX_PROGRESS = 10_000;
|
||||||
|
|
||||||
public ProgressAchievement() {
|
public ProgressAchievement() {
|
||||||
super(ProgressAchievementJSON.class);
|
super(ProgressAchievementJSON.class);
|
||||||
@@ -14,6 +16,13 @@ public class ProgressAchievement extends RCONMessage<ProgressAchievement.Progres
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(Gson gson, ProgressAchievementJSON json) {
|
public void handle(Gson gson, ProgressAchievementJSON json) {
|
||||||
|
int maxProgress = parseMaxProgress(Emulator.getConfig().getValue("rcon.achievement.max_progress", String.valueOf(DEFAULT_MAX_PROGRESS)));
|
||||||
|
if (json.progress > maxProgress) {
|
||||||
|
this.status = RCONMessage.STATUS_ERROR;
|
||||||
|
this.message = "progress must be between 1 and " + maxProgress;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
|
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
|
||||||
|
|
||||||
if (habbo != null) {
|
if (habbo != null) {
|
||||||
@@ -28,14 +37,27 @@ public class ProgressAchievement extends RCONMessage<ProgressAchievement.Progres
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parseMaxProgress(String configured) {
|
||||||
|
try {
|
||||||
|
int parsed = Integer.parseInt(configured);
|
||||||
|
if (parsed > 0) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_MAX_PROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
static class ProgressAchievementJSON {
|
static class ProgressAchievementJSON {
|
||||||
|
|
||||||
|
@Positive(message = "invalid user")
|
||||||
public int user_id;
|
public int user_id;
|
||||||
|
|
||||||
|
@Positive(message = "invalid achievement")
|
||||||
public int achievement_id;
|
public int achievement_id;
|
||||||
|
|
||||||
|
@Positive(message = "invalid progress")
|
||||||
public int progress;
|
public int progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.eu.habbo.messages.rcon;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class MuteUserGuardTest {
|
||||||
|
@Test
|
||||||
|
void parsesInvalidDurationCeilingsAsDefault() {
|
||||||
|
assertEquals(MuteUser.DEFAULT_MAX_DURATION_SECONDS, MuteUser.parseMaxDuration(null));
|
||||||
|
assertEquals(MuteUser.DEFAULT_MAX_DURATION_SECONDS, MuteUser.parseMaxDuration("-1"));
|
||||||
|
assertEquals(0, MuteUser.parseMaxDuration("0"));
|
||||||
|
assertEquals(60, MuteUser.parseMaxDuration("60"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsNegativeAndOversizedMuteDurations() throws Exception {
|
||||||
|
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/MuteUser.java"));
|
||||||
|
|
||||||
|
assertTrue(source.contains("json.duration < 0 || json.duration > maxDuration"),
|
||||||
|
"RCON mute must reject negative durations and configured-duration overflows");
|
||||||
|
assertTrue(source.contains("json.duration == 0 ? 0 : Emulator.getIntUnixTimestamp() + json.duration"),
|
||||||
|
"Offline unmute must clear mute_end_timestamp instead of writing the current timestamp");
|
||||||
|
assertTrue(source.contains("rcon.mute.max_duration_seconds"),
|
||||||
|
"RCON mute duration ceiling must be configurable");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.eu.habbo.messages.rcon;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ProgressAchievementGuardTest {
|
||||||
|
@Test
|
||||||
|
void parsesInvalidProgressCeilingsAsDefault() {
|
||||||
|
assertEquals(ProgressAchievement.DEFAULT_MAX_PROGRESS, ProgressAchievement.parseMaxProgress(null));
|
||||||
|
assertEquals(ProgressAchievement.DEFAULT_MAX_PROGRESS, ProgressAchievement.parseMaxProgress("0"));
|
||||||
|
assertEquals(50, ProgressAchievement.parseMaxProgress("50"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatesAchievementProgressPayload() throws Exception {
|
||||||
|
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/ProgressAchievement.java"));
|
||||||
|
|
||||||
|
assertTrue(source.contains("@Positive(message = \"invalid user\")"),
|
||||||
|
"RCON achievement progress must reject invalid target users before execution");
|
||||||
|
assertTrue(source.contains("@Positive(message = \"invalid achievement\")"),
|
||||||
|
"RCON achievement progress must reject invalid achievement ids before execution");
|
||||||
|
assertTrue(source.contains("@Positive(message = \"invalid progress\")"),
|
||||||
|
"RCON achievement progress must reject zero or negative progress before execution");
|
||||||
|
assertTrue(source.contains("json.progress > maxProgress"),
|
||||||
|
"RCON achievement progress must reject configured-progress overflows");
|
||||||
|
assertTrue(source.contains("rcon.achievement.max_progress"),
|
||||||
|
"RCON achievement progress ceiling must be configurable");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user