You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
fix(rcon): validate grant requests
This commit is contained in:
@@ -21,6 +21,18 @@ public class GiveCredits extends RCONMessage<GiveCredits.JSONGiveCredits> {
|
||||
|
||||
@Override
|
||||
public void handle(Gson gson, JSONGiveCredits object) {
|
||||
int maxAmount = RconGrantGuard.parseMaxAmount(
|
||||
Emulator.getConfig().getValue("rcon.grant.max_amount", String.valueOf(RconGrantGuard.DEFAULT_MAX_AMOUNT)));
|
||||
String validationError = RconGrantGuard.validateUserId(object.user_id);
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validatePositiveAmount(object.credits, maxAmount, "credits");
|
||||
}
|
||||
if (validationError != null) {
|
||||
this.status = RCONMessage.STATUS_ERROR;
|
||||
this.message = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(object.user_id);
|
||||
|
||||
if (habbo != null) {
|
||||
|
||||
@@ -21,11 +21,29 @@ public class GivePixels extends RCONMessage<GivePixels.JSONGivePixels> {
|
||||
|
||||
@Override
|
||||
public void handle(Gson gson, JSONGivePixels object) {
|
||||
int maxAmount = RconGrantGuard.parseMaxAmount(
|
||||
Emulator.getConfig().getValue("rcon.grant.max_amount", String.valueOf(RconGrantGuard.DEFAULT_MAX_AMOUNT)));
|
||||
String validationError = RconGrantGuard.validateUserId(object.user_id);
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validatePositiveAmount(object.pixels, maxAmount, "pixels");
|
||||
}
|
||||
if (validationError != null) {
|
||||
this.status = RCONMessage.STATUS_ERROR;
|
||||
this.message = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(object.user_id);
|
||||
|
||||
if (habbo != null) {
|
||||
habbo.givePixels(object.pixels);
|
||||
} else {
|
||||
if (!RconUserLookup.userExists(object.user_id)) {
|
||||
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||
this.message = "user not found";
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO users_currency (`user_id`, `type`, `amount`) VALUES (?, 0, ?) ON DUPLICATE KEY UPDATE amount = amount + ?")) {
|
||||
statement.setInt(1, object.user_id);
|
||||
statement.setInt(2, object.pixels);
|
||||
|
||||
@@ -22,11 +22,32 @@ public class GivePoints extends RCONMessage<GivePoints.JSONGivePoints> {
|
||||
|
||||
@Override
|
||||
public void handle(Gson gson, JSONGivePoints object) {
|
||||
int maxAmount = RconGrantGuard.parseMaxAmount(
|
||||
Emulator.getConfig().getValue("rcon.grant.max_amount", String.valueOf(RconGrantGuard.DEFAULT_MAX_AMOUNT)));
|
||||
String validationError = RconGrantGuard.validateUserId(object.user_id);
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validateCurrencyType(object.type);
|
||||
}
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validatePositiveAmount(object.points, maxAmount, "points");
|
||||
}
|
||||
if (validationError != null) {
|
||||
this.status = RCONMessage.STATUS_ERROR;
|
||||
this.message = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(object.user_id);
|
||||
|
||||
if (habbo != null) {
|
||||
habbo.givePoints(object.type, object.points);
|
||||
} else {
|
||||
if (!RconUserLookup.userExists(object.user_id)) {
|
||||
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||
this.message = "user not found";
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO users_currency (`user_id`, `type`, `amount`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = amount + ?")) {
|
||||
statement.setInt(1, object.user_id);
|
||||
statement.setInt(2, object.type);
|
||||
|
||||
@@ -21,6 +21,27 @@ public class GiveRespect extends RCONMessage<GiveRespect.JSONGiveRespect> {
|
||||
|
||||
@Override
|
||||
public void handle(Gson gson, JSONGiveRespect object) {
|
||||
int maxAmount = RconGrantGuard.parseMaxAmount(
|
||||
Emulator.getConfig().getValue("rcon.grant.max_amount", String.valueOf(RconGrantGuard.DEFAULT_MAX_AMOUNT)));
|
||||
String validationError = RconGrantGuard.validateUserId(object.user_id);
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validateNonNegativeAmount(object.respect_given, maxAmount, "respect_given");
|
||||
}
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validateNonNegativeAmount(object.respect_received, maxAmount, "respect_received");
|
||||
}
|
||||
if (validationError == null) {
|
||||
validationError = RconGrantGuard.validateNonNegativeAmount(object.daily_respects, maxAmount, "daily_respects");
|
||||
}
|
||||
if (validationError == null && object.respect_given == 0 && object.respect_received == 0 && object.daily_respects == 0) {
|
||||
validationError = "no respect grant provided";
|
||||
}
|
||||
if (validationError != null) {
|
||||
this.status = RCONMessage.STATUS_ERROR;
|
||||
this.message = validationError;
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(object.user_id);
|
||||
|
||||
if (habbo != null) {
|
||||
@@ -29,15 +50,26 @@ public class GiveRespect extends RCONMessage<GiveRespect.JSONGiveRespect> {
|
||||
habbo.getHabboStats().respectPointsToGive += object.daily_respects;
|
||||
habbo.getClient().sendResponse(new UserDataComposer(habbo));
|
||||
} else {
|
||||
if (!RconUserLookup.userExists(object.user_id)) {
|
||||
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||
this.message = "user not found";
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET respects_given = respects_given + ?, respects_received = respects_received + ?, daily_respect_points = daily_respect_points + ? WHERE user_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, object.respect_given);
|
||||
statement.setInt(2, object.respect_received);
|
||||
statement.setInt(3, object.daily_respects);
|
||||
statement.setInt(4, object.user_id);
|
||||
statement.execute();
|
||||
if (statement.executeUpdate() == 0) {
|
||||
this.status = RCONMessage.HABBO_NOT_FOUND;
|
||||
this.message = "user not found";
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
this.status = RCONMessage.SYSTEM_ERROR;
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.message = "offline";
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.eu.habbo.messages.rcon;
|
||||
|
||||
public class RconGrantGuard {
|
||||
public static final int DEFAULT_MAX_AMOUNT = 1_000_000;
|
||||
|
||||
private RconGrantGuard() {
|
||||
}
|
||||
|
||||
public static String validateUserId(int userId) {
|
||||
return userId > 0 ? null : "invalid user";
|
||||
}
|
||||
|
||||
public static String validatePositiveAmount(int amount, int maxAmount, String fieldName) {
|
||||
if (amount <= 0) {
|
||||
return "invalid " + fieldName;
|
||||
}
|
||||
|
||||
if (maxAmount > 0 && amount > maxAmount) {
|
||||
return fieldName + " exceeds rcon grant ceiling";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String validateNonNegativeAmount(int amount, int maxAmount, String fieldName) {
|
||||
if (amount < 0) {
|
||||
return "invalid " + fieldName;
|
||||
}
|
||||
|
||||
if (maxAmount > 0 && amount > maxAmount) {
|
||||
return fieldName + " exceeds rcon grant ceiling";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String validateCurrencyType(int type) {
|
||||
return type >= 0 ? null : "invalid currency type";
|
||||
}
|
||||
|
||||
public static int parseMaxAmount(String rawValue) {
|
||||
try {
|
||||
int value = Integer.parseInt(rawValue);
|
||||
return value > 0 ? value : DEFAULT_MAX_AMOUNT;
|
||||
} catch (Exception e) {
|
||||
return DEFAULT_MAX_AMOUNT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.eu.habbo.messages.rcon;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public class RconUserLookup {
|
||||
private RconUserLookup() {
|
||||
}
|
||||
|
||||
public static boolean userExists(int userId) {
|
||||
if (Emulator.getGameEnvironment().getHabboManager().getHabbo(userId) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE id = ? LIMIT 1")) {
|
||||
statement.setInt(1, userId);
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
return set.next();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,7 @@ class GiveCreditsContractTest {
|
||||
"Offline RCON credit grants must inspect the affected row count");
|
||||
assertTrue(source.contains("HABBO_NOT_FOUND"),
|
||||
"Offline RCON credit grants must report missing users when the UPDATE changes no rows");
|
||||
assertTrue(source.contains("RconGrantGuard.validatePositiveAmount"),
|
||||
"RCON credit grants must reject zero, negative, and oversized grants");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,9 @@ class GivePixelsContractTest {
|
||||
"Offline RCON pixel grants must create the users_currency type 0 row when it is missing");
|
||||
assertTrue(source.contains("ON DUPLICATE KEY UPDATE"),
|
||||
"Offline RCON pixel grants should increment existing rows with an upsert");
|
||||
assertTrue(source.contains("RconGrantGuard.validatePositiveAmount"),
|
||||
"RCON pixel grants must reject zero, negative, and oversized grants");
|
||||
assertTrue(source.contains("RconUserLookup.userExists"),
|
||||
"Offline RCON pixel grants must not create orphan currency rows for missing users");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
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.assertTrue;
|
||||
|
||||
class GivePointsContractTest {
|
||||
private static String givePointsSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/GivePoints.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pointGrantsValidateAmountTypeAndOfflineUserExistence() throws Exception {
|
||||
String source = givePointsSource();
|
||||
|
||||
assertTrue(source.contains("RconGrantGuard.validateCurrencyType"),
|
||||
"RCON point grants must reject invalid currency types");
|
||||
assertTrue(source.contains("RconGrantGuard.validatePositiveAmount"),
|
||||
"RCON point grants must reject zero, negative, and oversized grants");
|
||||
assertTrue(source.contains("RconUserLookup.userExists"),
|
||||
"Offline RCON point grants must not create orphan currency rows for missing users");
|
||||
assertTrue(source.contains("ON DUPLICATE KEY UPDATE"),
|
||||
"Offline RCON point grants should increment existing rows with an upsert");
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,9 @@ class GiveRespectContractTest {
|
||||
"respects_given must be incremented with respect_given");
|
||||
assertTrue(source.contains("statement.setInt(2, object.respect_received);"),
|
||||
"respects_received must be incremented with respect_received");
|
||||
assertTrue(source.contains("RconGrantGuard.validateNonNegativeAmount"),
|
||||
"RCON respect grants must reject negative values");
|
||||
assertTrue(source.contains("statement.executeUpdate() == 0"),
|
||||
"Offline RCON respect grants must report missing users when the UPDATE changes no rows");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eu.habbo.messages.rcon;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class RconGrantGuardTest {
|
||||
@Test
|
||||
void validatesPositiveGrantAmounts() {
|
||||
assertNull(RconGrantGuard.validatePositiveAmount(1, 100, "credits"));
|
||||
assertEquals("invalid credits", RconGrantGuard.validatePositiveAmount(0, 100, "credits"));
|
||||
assertEquals("invalid credits", RconGrantGuard.validatePositiveAmount(-1, 100, "credits"));
|
||||
assertEquals("credits exceeds rcon grant ceiling", RconGrantGuard.validatePositiveAmount(101, 100, "credits"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesNonNegativeGrantAmounts() {
|
||||
assertNull(RconGrantGuard.validateNonNegativeAmount(0, 100, "respect_given"));
|
||||
assertEquals("invalid respect_given", RconGrantGuard.validateNonNegativeAmount(-1, 100, "respect_given"));
|
||||
assertEquals("respect_given exceeds rcon grant ceiling", RconGrantGuard.validateNonNegativeAmount(101, 100, "respect_given"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validatesUserAndCurrencyIdentifiers() {
|
||||
assertNull(RconGrantGuard.validateUserId(1));
|
||||
assertEquals("invalid user", RconGrantGuard.validateUserId(0));
|
||||
assertNull(RconGrantGuard.validateCurrencyType(0));
|
||||
assertEquals("invalid currency type", RconGrantGuard.validateCurrencyType(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsesInvalidGrantCeilingsAsDefault() {
|
||||
assertEquals(RconGrantGuard.DEFAULT_MAX_AMOUNT, RconGrantGuard.parseMaxAmount(null));
|
||||
assertEquals(RconGrantGuard.DEFAULT_MAX_AMOUNT, RconGrantGuard.parseMaxAmount("0"));
|
||||
assertEquals(500, RconGrantGuard.parseMaxAmount("500"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user