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): guard user update mutations
This commit is contained in:
@@ -12,9 +12,13 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class UpdateUser extends RCONMessage<UpdateUser.JSON> {
|
public class UpdateUser extends RCONMessage<UpdateUser.JSON> {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateUser.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateUser.class);
|
||||||
|
static final int DEFAULT_MAX_ACHIEVEMENT_SCORE_DELTA = 10_000;
|
||||||
|
static final int MAX_LOOK_LENGTH = 256;
|
||||||
|
private static final Pattern LOOK_PATTERN = Pattern.compile("^[A-Za-z0-9.-]{1,256}$");
|
||||||
|
|
||||||
public UpdateUser() {
|
public UpdateUser() {
|
||||||
super(UpdateUser.JSON.class);
|
super(UpdateUser.JSON.class);
|
||||||
@@ -23,6 +27,19 @@ public class UpdateUser extends RCONMessage<UpdateUser.JSON> {
|
|||||||
@Override
|
@Override
|
||||||
public void handle(Gson gson, JSON json) {
|
public void handle(Gson gson, JSON json) {
|
||||||
if (json.user_id > 0) {
|
if (json.user_id > 0) {
|
||||||
|
int maxAchievementScoreDelta = parseMaxAchievementScoreDelta(Emulator.getConfig().getValue("rcon.updateuser.max_achievement_score_delta", String.valueOf(DEFAULT_MAX_ACHIEVEMENT_SCORE_DELTA)));
|
||||||
|
if (!isValidAchievementScoreDelta(json.achievement_score, maxAchievementScoreDelta)) {
|
||||||
|
this.status = RCONMessage.STATUS_ERROR;
|
||||||
|
this.message = "invalid achievement score";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidLook(json.look)) {
|
||||||
|
this.status = RCONMessage.STATUS_ERROR;
|
||||||
|
this.message = "invalid look";
|
||||||
|
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) {
|
||||||
@@ -97,23 +114,56 @@ public class UpdateUser extends RCONMessage<UpdateUser.JSON> {
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
statement.setInt(index, json.user_id);
|
statement.setInt(index, json.user_id);
|
||||||
statement.execute();
|
if (statement.executeUpdate() == 0) {
|
||||||
|
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||||
|
this.message = "user not found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!json.look.isEmpty()) {
|
if (!json.look.isEmpty()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement("UPDATE users SET look = ? WHERE id = ? LIMIT 1")) {
|
try (PreparedStatement statement = connection.prepareStatement("UPDATE users SET look = ? WHERE id = ? LIMIT 1")) {
|
||||||
statement.setString(1, json.look);
|
statement.setString(1, json.look);
|
||||||
statement.setInt(2, json.user_id);
|
statement.setInt(2, json.user_id);
|
||||||
statement.execute();
|
if (statement.executeUpdate() == 0) {
|
||||||
|
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||||
|
this.message = "user not found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
LOGGER.error("Caught SQL exception", e);
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
this.status = RCONMessage.SYSTEM_ERROR;
|
||||||
|
this.message = "failed to update user";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.status = RCONMessage.STATUS_ERROR;
|
||||||
|
this.message = "invalid user";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isValidAchievementScoreDelta(int achievementScoreDelta, int maxAchievementScoreDelta) {
|
||||||
|
return achievementScoreDelta >= 0 && achievementScoreDelta <= maxAchievementScoreDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseMaxAchievementScoreDelta(String configured) {
|
||||||
|
try {
|
||||||
|
int parsed = Integer.parseInt(configured);
|
||||||
|
if (parsed >= 0) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_MAX_ACHIEVEMENT_SCORE_DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isValidLook(String look) {
|
||||||
|
return look == null || look.isEmpty() || (look.length() <= MAX_LOOK_LENGTH && LOOK_PATTERN.matcher(look).matches());
|
||||||
|
}
|
||||||
|
|
||||||
static class JSON {
|
static class JSON {
|
||||||
|
|
||||||
public int user_id;
|
public int user_id;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class UpdateUserGuardTest {
|
||||||
|
@Test
|
||||||
|
void validatesAchievementScoreDelta() {
|
||||||
|
assertTrue(UpdateUser.isValidAchievementScoreDelta(0, 100));
|
||||||
|
assertTrue(UpdateUser.isValidAchievementScoreDelta(100, 100));
|
||||||
|
assertFalse(UpdateUser.isValidAchievementScoreDelta(-1, 100));
|
||||||
|
assertFalse(UpdateUser.isValidAchievementScoreDelta(101, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parsesInvalidAchievementScoreCeilingsAsDefault() {
|
||||||
|
assertEquals(UpdateUser.DEFAULT_MAX_ACHIEVEMENT_SCORE_DELTA, UpdateUser.parseMaxAchievementScoreDelta(null));
|
||||||
|
assertEquals(UpdateUser.DEFAULT_MAX_ACHIEVEMENT_SCORE_DELTA, UpdateUser.parseMaxAchievementScoreDelta("-1"));
|
||||||
|
assertEquals(0, UpdateUser.parseMaxAchievementScoreDelta("0"));
|
||||||
|
assertEquals(50, UpdateUser.parseMaxAchievementScoreDelta("50"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatesLookShapeAndLength() {
|
||||||
|
assertTrue(UpdateUser.isValidLook(null));
|
||||||
|
assertTrue(UpdateUser.isValidLook(""));
|
||||||
|
assertTrue(UpdateUser.isValidLook("hr-115-42.hd-195-19.ch-3030-82"));
|
||||||
|
assertFalse(UpdateUser.isValidLook("hd-1\nch-1"));
|
||||||
|
assertFalse(UpdateUser.isValidLook("hd_1"));
|
||||||
|
assertFalse(UpdateUser.isValidLook("a".repeat(UpdateUser.MAX_LOOK_LENGTH + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void offlineUpdatesReportMissingUsersAndUseAffectedRows() throws Exception {
|
||||||
|
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/UpdateUser.java"));
|
||||||
|
|
||||||
|
assertTrue(source.contains("executeUpdate() == 0"),
|
||||||
|
"Offline UpdateUser mutations must inspect affected row counts");
|
||||||
|
assertTrue(source.contains("HABBO_NOT_FOUND"),
|
||||||
|
"Offline UpdateUser mutations must report missing users");
|
||||||
|
assertTrue(source.contains("rcon.updateuser.max_achievement_score_delta"),
|
||||||
|
"Achievement score deltas must have a configurable RCON ceiling");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user