From 8db6281cc80ed81d04f609e4017fd919c4b89ee3 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 14 Jun 2026 20:19:45 +0200 Subject: [PATCH] fix(guilds): only accept pending memberships Guard the guild acceptance update with level_id = REQUESTED so a stale or concurrent accept cannot promote a membership row that has already changed state. Tests: mvn '-Dtest=GuildManagerMembershipContractTest,GuildMembershipManagementContractTest,GuildMembershipRequestContractTest' test --- .../habbo/habbohotel/guilds/GuildManager.java | 3 ++- .../GuildManagerMembershipContractTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/habbohotel/guilds/GuildManagerMembershipContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java index b835a5f5..9441fc11 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java @@ -291,11 +291,12 @@ public class GuildManager { } } } else if (!error) { - try (PreparedStatement statement = connection.prepareStatement("UPDATE guilds_members SET level_id = ?, member_since = ? WHERE user_id = ? AND guild_id = ?")) { + try (PreparedStatement statement = connection.prepareStatement("UPDATE guilds_members SET level_id = ?, member_since = ? WHERE user_id = ? AND guild_id = ? AND level_id = ?")) { statement.setInt(1, GuildRank.MEMBER.type); statement.setInt(2, Emulator.getIntUnixTimestamp()); statement.setInt(3, userId); statement.setInt(4, guild.getId()); + statement.setInt(5, GuildRank.REQUESTED.type); statement.execute(); } } diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/guilds/GuildManagerMembershipContractTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/guilds/GuildManagerMembershipContractTest.java new file mode 100644 index 00000000..20a599c1 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/guilds/GuildManagerMembershipContractTest.java @@ -0,0 +1,24 @@ +package com.eu.habbo.habbohotel.guilds; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +class GuildManagerMembershipContractTest { + private static String guildManagerSource() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java")); + } + + @Test + void acceptRequestOnlyPromotesPendingMembershipRows() throws Exception { + String source = guildManagerSource(); + + assertTrue(source.contains("UPDATE guilds_members SET level_id = ?, member_since = ? WHERE user_id = ? AND guild_id = ? AND level_id = ?"), + "accepting a guild request must only promote rows still in REQUESTED state"); + assertTrue(source.contains("statement.setInt(5, GuildRank.REQUESTED.type);"), + "the accept-request update must bind the expected REQUESTED rank guard"); + } +}