From ac7e04829e4f203fc4acb92fe4a8bd06215f650f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sat, 20 Dec 2025 17:46:02 +0100 Subject: [PATCH 1/4] introduced `ReportCreatedListener`, updated `ReportCreatedEvent` to use UUIDs for reporter and reported, and improved admin notifications for new reports --- .../metaGameplay/report/Report.java | 8 ++++++ .../report/ReportCreatedListener.java | 26 +++++++++++++++++++ .../antiInventoryMove/AntiInventoryMove.java | 1 + .../api/server/hooks/impl/WebsiteHook.java | 4 ++- .../spawn/core/event/ReportCreatedEvent.java | 4 ++- 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/ReportCreatedListener.java diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/Report.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/Report.java index 543abe0..5a2098b 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/Report.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/Report.java @@ -16,6 +16,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -178,6 +179,13 @@ public class Report extends Appliance { issuer.sendMessage(component.build()); } + @Override + protected @NotNull List listeners() { + return List.of( + new ReportCreatedListener() + ); + } + @Override @NotNull protected List> commands() { diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/ReportCreatedListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/ReportCreatedListener.java new file mode 100644 index 0000000..8a6c988 --- /dev/null +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/metaGameplay/report/ReportCreatedListener.java @@ -0,0 +1,26 @@ +package eu.mhsl.craftattack.spawn.common.appliances.metaGameplay.report; + +import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.core.event.ReportCreatedEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; + +public class ReportCreatedListener extends ApplianceListener { + @EventHandler + public void onReport(ReportCreatedEvent event) { + OfflinePlayer reporter = Bukkit.getOfflinePlayer(event.getReport().reporter()); + OfflinePlayer reported = Bukkit.getOfflinePlayer(event.getReport().reported()); + + Component message = Component.text( + "\uD83D\uDD14 Neuer Report von %s gegen %s: %s".formatted(reporter.getName(), reported.getName(), event.getReport().reason()), + NamedTextColor.YELLOW + ); + + Bukkit.getOnlinePlayers().stream() + .filter(player -> player.hasPermission("admin")) + .forEach(player -> player.sendMessage(message)); + } +} diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/AntiInventoryMove.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/AntiInventoryMove.java index 2b7a21d..ee99f6a 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/AntiInventoryMove.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/AntiInventoryMove.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +@Appliance.Flags(enabled = false) public class AntiInventoryMove extends Appliance { private static final long errorTimeMargin = Ticks.SINGLE_TICK_DURATION_MS * 2; diff --git a/core/src/main/java/eu/mhsl/craftattack/spawn/core/api/server/hooks/impl/WebsiteHook.java b/core/src/main/java/eu/mhsl/craftattack/spawn/core/api/server/hooks/impl/WebsiteHook.java index 73e7552..c75d02b 100644 --- a/core/src/main/java/eu/mhsl/craftattack/spawn/core/api/server/hooks/impl/WebsiteHook.java +++ b/core/src/main/java/eu/mhsl/craftattack/spawn/core/api/server/hooks/impl/WebsiteHook.java @@ -28,14 +28,16 @@ public class WebsiteHook extends HttpHook { return HttpServer.nothing; })); - record CreatedReport(String reporter, String reported, String reason) {} + record CreatedReport(UUID reporter, UUID reported, String reason) {} this.addAction("report", new JsonAction<>(CreatedReport.class, createdReport -> { + Main.logger().info(String.format("New Report from Hook: (%s) Reporter: %s Reported: %s", createdReport.reason, createdReport.reporter, createdReport.reported)); SpawnEvent.call(new ReportCreatedEvent(new ReportCreatedEvent.CreatedReport(createdReport.reporter, createdReport.reported, createdReport.reason))); return HttpServer.nothing; })); record CreatedStrike(UUID uuid) {} this.addAction("strike", new JsonAction<>(CreatedStrike.class, createdStrike -> { + Main.logger().info(String.format("New Strike from Hook! (User %s)", createdStrike.uuid)); SpawnEvent.call(new StrikeCreatedEvent(new StrikeCreatedEvent.CreatedStrike(createdStrike.uuid))); return HttpServer.nothing; })); diff --git a/core/src/main/java/eu/mhsl/craftattack/spawn/core/event/ReportCreatedEvent.java b/core/src/main/java/eu/mhsl/craftattack/spawn/core/event/ReportCreatedEvent.java index 777c02c..6b41367 100644 --- a/core/src/main/java/eu/mhsl/craftattack/spawn/core/event/ReportCreatedEvent.java +++ b/core/src/main/java/eu/mhsl/craftattack/spawn/core/event/ReportCreatedEvent.java @@ -4,6 +4,8 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; +import java.util.UUID; + public class ReportCreatedEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); @Override @@ -15,7 +17,7 @@ public class ReportCreatedEvent extends Event { return HANDLERS; } - public record CreatedReport(String reporter, String reported, String reason) {} + public record CreatedReport(UUID reporter, UUID reported, String reason) {} private final CreatedReport report; public ReportCreatedEvent(CreatedReport report) { From a257b604ea9a5f1f03205dccc24b25a38ce0f1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 21 Dec 2025 09:32:40 +0100 Subject: [PATCH 2/4] updated `InventoryTrackerListener` to monitor `InventoryOpenEvent` with high priority and prevent processing of cancelled events --- .../security/antiInventoryMove/InventoryTrackerListener.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/InventoryTrackerListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/InventoryTrackerListener.java index 0c5c89f..7e064d4 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/InventoryTrackerListener.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiInventoryMove/InventoryTrackerListener.java @@ -3,13 +3,15 @@ package eu.mhsl.craftattack.spawn.common.appliances.security.antiInventoryMove; import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryOpenEvent; class InventoryTrackerListener extends ApplianceListener { - @EventHandler + @EventHandler(priority = EventPriority.MONITOR) public void onOpen(InventoryOpenEvent event) { if(!(event.getPlayer() instanceof Player player)) return; + if(event.isCancelled()) return; this.getAppliance().setInvOpen(player, true); } From 71f2da8e99742e4be33aa3255f7c34987d70e4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 22 Dec 2025 18:44:22 +0100 Subject: [PATCH 3/4] introduced `AntiIllegalSignCharacters` appliance to prevent usage of illegal characters in sign edits --- .../AntiIllegalSignCharacters.java | 16 +++++++++++++ .../SignEditListener.java | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/AntiIllegalSignCharacters.java create mode 100644 common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/AntiIllegalSignCharacters.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/AntiIllegalSignCharacters.java new file mode 100644 index 0000000..9a94b66 --- /dev/null +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/AntiIllegalSignCharacters.java @@ -0,0 +1,16 @@ +package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters; + +import eu.mhsl.craftattack.spawn.core.appliance.Appliance; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class AntiIllegalSignCharacters extends Appliance { + @Override + protected @NotNull List listeners() { + return List.of( + new SignEditListener() + ); + } +} diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java new file mode 100644 index 0000000..9ad38c9 --- /dev/null +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java @@ -0,0 +1,23 @@ +package eu.mhsl.craftattack.spawn.common.appliances.security.antiIllegalSignCharacters; + +import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.SignChangeEvent; + +class SignEditListener extends ApplianceListener { + @EventHandler + public void onSignEdit(SignChangeEvent event) { + for (int i = 0; i < 4; i++) { + Component line = event.line(i); + if(line == null) continue; + String lineStr = PlainTextComponentSerializer.plainText().serialize(line); + + if (!lineStr.matches("^[ -~]*$")) { + String cleaned = lineStr.replaceAll("[^ -~]", ""); + event.line(i, Component.text(cleaned)); + } + } + } +} From 89c1c4335bb1b2f6f5ba1adc20584f6841148be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 22 Dec 2025 21:41:34 +0100 Subject: [PATCH 4/4] refined `SignEditListener` to filter illegal characters more robustly by normalizing input and introducing specific character whitelists --- .../SignEditListener.java | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java index 9ad38c9..4a7cd94 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java @@ -6,18 +6,73 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.event.EventHandler; import org.bukkit.event.block.SignChangeEvent; +import java.text.Normalizer; +import java.util.Set; + class SignEditListener extends ApplianceListener { + private static final Set ALLOWED_CHARS = Set.of( + (int)' ', (int)'.', (int)',', (int)';', (int)':', (int)'!', (int)'?', + (int)'"', (int)'\'', + (int)'(', (int)')', (int)'[', (int)']', (int)'{', (int)'}', + (int)'-', (int)'_', (int)'+', (int)'=', (int)'/', (int)'\\', + (int)'@', (int)'#', (int)'$', (int)'%', (int)'&', (int)'*', + (int)'<', (int)'>', (int)'|', + (int)'~', (int)'`', (int)'^' + ); + + private static final Set ALLOWED_EXTRA = Set.of( + (int)'Ä', (int)'Ö', (int)'Ü', (int)'ä', (int)'ö', (int)'ü', (int)'ß', + (int)'€', (int)'°', (int)'µ' + ); + + @EventHandler public void onSignEdit(SignChangeEvent event) { for (int i = 0; i < 4; i++) { Component line = event.line(i); - if(line == null) continue; - String lineStr = PlainTextComponentSerializer.plainText().serialize(line); - - if (!lineStr.matches("^[ -~]*$")) { - String cleaned = lineStr.replaceAll("[^ -~]", ""); - event.line(i, Component.text(cleaned)); - } + if (line == null) continue; + String plainString = PlainTextComponentSerializer.plainText().serialize(line); + plainString = Normalizer.normalize(plainString, Normalizer.Form.NFC); + String cleaned = filterAllowed(plainString); + event.line(i, Component.text(cleaned)); } } + + private static String filterAllowed(String s) { + StringBuilder out = new StringBuilder(s.length()); + + for (int off = 0; off < s.length(); ) { + int cp = s.codePointAt(off); + off += Character.charCount(cp); + + if (isForbidden(cp)) continue; + + if (Character.isLetterOrDigit(cp)) { + out.appendCodePoint(cp); + continue; + } + + if (ALLOWED_CHARS.contains(cp) || ALLOWED_EXTRA.contains(cp)) { + out.appendCodePoint(cp); + } + } + return out.toString(); + } + + private static boolean isForbidden(int cp) { + // Surrogates / invalid + if (cp >= 0xD800 && cp <= 0xDFFF) return true; + + // Private Use Area (Mod/Pack-Icons/Placeholder) + if (cp >= 0xE000 && cp <= 0xF8FF) return true; + + // Zero-width and control characters + if (cp == 0x200B || cp == 0x200C || cp == 0x200D || cp == 0xFEFF) return true; + + // BiDi-Steuerzeichen + if (cp >= 0x202A && cp <= 0x202E) return true; + if (cp >= 0x2066 && cp <= 0x2069) return true; + + return cp == '§'; + } }