From 2c92a8b52f9ac2c21792a47d831201b157a17033 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 28 May 2016 22:32:45 +0200 Subject: [PATCH 1/9] Move logic for join events in its own listener --- src/main/java/fr/xephi/authme/AuthMe.java | 13 +- .../listener/AuthMePlayerJoinListener.java | 233 ++++++++++++++++++ .../authme/listener/AuthMePlayerListener.java | 175 +------------ .../listener/ListenerConsistencyTest.java | 8 +- 4 files changed, 240 insertions(+), 189 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 05023a28..98f6cca2 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -22,6 +22,7 @@ import fr.xephi.authme.initialization.MetricsStarter; import fr.xephi.authme.listener.AuthMeBlockListener; import fr.xephi.authme.listener.AuthMeEntityListener; import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; +import fr.xephi.authme.listener.AuthMePlayerJoinListener; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.listener.AuthMePlayerListener16; import fr.xephi.authme.listener.AuthMePlayerListener18; @@ -34,7 +35,6 @@ import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.SHA256; @@ -360,6 +360,7 @@ public class AuthMe extends JavaPlugin { pluginManager.registerEvents(initializer.get(AuthMeBlockListener.class), this); pluginManager.registerEvents(initializer.get(AuthMeEntityListener.class), this); pluginManager.registerEvents(initializer.get(AuthMeServerListener.class), this); + pluginManager.registerEvents(initializer.get(AuthMePlayerJoinListener.class), this); // Try to register 1.6 player listeners try { @@ -650,16 +651,6 @@ public class AuthMe extends JavaPlugin { return pluginHooks != null && pluginHooks.isNpc(player) || player.hasMetadata("NPC"); } - // Select the player to kick when a vip player joins the server when full - public Player generateKickPlayer(Collection collection) { - for (Player player : collection) { - if (!getPermissionsManager().hasPermission(player, PlayerStatePermission.IS_VIP)) { - return player; - } - } - return null; - } - // Purge inactive players from the database, as defined in the configuration private void runAutoPurge() { if (!newSettings.getProperty(PurgeSettings.USE_AUTO_PURGE) || autoPurging) { diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java new file mode 100644 index 00000000..c83d77a5 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java @@ -0,0 +1,233 @@ +package fr.xephi.authme.listener; + +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import fr.xephi.authme.AntiBot; +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.process.Management; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.ProtectionSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.Utils; +import fr.xephi.authme.util.ValidationService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.Collection; +import java.util.regex.Pattern; + +/** + * Listener for player join events. + */ +public class AuthMePlayerJoinListener implements Listener, Reloadable { + + @Inject + private BukkitService bukkitService; + @Inject + private DataSource dataSource; + @Inject + private AntiBot antiBot; + @Inject + private Management management; + @Inject + private NewSetting settings; + @Inject + private Messages m; + @Inject + private PermissionsManager permissionsManager; + @Inject + private ValidationService validationService; + @Inject + private AuthMe plugin; + + private Pattern nicknamePattern; + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerJoin(PlayerJoinEvent event) { + final Player player = event.getPlayer(); + if (player == null) { + return; + } + + // Schedule login task so works after the prelogin + // (Fix found by Koolaid5000) + bukkitService.runTask(new Runnable() { + @Override + public void run() { + management.performJoin(player); + } + }); + } + + // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode + @EventHandler(priority = EventPriority.HIGHEST) + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + PlayerAuth auth = dataSource.getAuth(event.getName()); + if (auth == null && antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE) { + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + antiBot.antibotKicked.addIfAbsent(event.getName()); + return; + } + if (auth == null && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { + event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + return; + } + final String name = event.getName().toLowerCase(); + if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { + event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + return; + } + if (settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE) && auth != null && auth.getRealName() != null) { + String realName = auth.getRealName(); + if (!realName.isEmpty() && !"Player".equals(realName) && !realName.equals(event.getName())) { + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CASE, realName, event.getName())); + return; + } + if (realName.isEmpty() || "Player".equals(realName)) { + dataSource.updateRealName(event.getName().toLowerCase(), event.getName()); + } + } + + if (auth == null && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); + return; + } + } + + final Player player = bukkitService.getPlayerExact(name); + // Check if forceSingleSession is set to true, so kick player that has + // joined with same nick of online player + if (player != null && settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + event.setKickMessage(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR)); + LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); + if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { + Utils.addNormal(player, limbo.getGroup()); + LimboCache.getInstance().deleteLimboPlayer(name); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerLogin(PlayerLoginEvent event) { + final Player player = event.getPlayer(); + if (player == null || Utils.isUnrestricted(player)) { + return; + } + + if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) { + if (permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + int playersOnline = bukkitService.getOnlinePlayers().size(); + if (playersOnline > plugin.getServer().getMaxPlayers()) { + event.allow(); + } else { + Player pl = generateKickPlayer(bukkitService.getOnlinePlayers()); + if (pl != null) { + pl.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); + event.allow(); + } else { + ConsoleLogger.info("The player " + event.getPlayer().getName() + " tried to join, but the server was full"); + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + } + } + } else { + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + return; + } + } + + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + return; + } + + final String name = player.getName().toLowerCase(); + boolean isAuthAvailable = dataSource.isAuthAvailable(name); + + if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + antiBot.antibotKicked.addIfAbsent(player.getName()); + return; + } + + if (settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED) && !isAuthAvailable) { + event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + return; + } + + if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { + event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + return; + } + + if (name.equalsIgnoreCase("Player") || !nicknamePattern.matcher(player.getName()).matches()) { + event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS) + .replace("REG_EX", nicknamePattern.pattern())); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + return; + } + + antiBot.checkAntiBot(player); + + if (settings.getProperty(HooksSettings.BUNGEECORD)) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("IP"); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + } + + @PostConstruct + @Override + public void reload() { + String nickRegEx = settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS); + try { + nicknamePattern = Pattern.compile(nickRegEx); + } catch (Exception e) { + nicknamePattern = Pattern.compile(".*?"); + ConsoleLogger.showError("Nickname pattern is not a valid regular expression! " + + "Fallback to allowing all nicknames"); + } + } + + // Select the player to kick when a vip player joins the server when full + private Player generateKickPlayer(Collection collection) { + for (Player player : collection) { + if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + return player; + } + } + return null; + } +} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 307c9d31..a928b915 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,26 +1,16 @@ package fr.xephi.authme.listener; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AntiBot.AntiBotStatus; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; -import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.cache.limbo.LimboCache; -import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.BukkitService; @@ -37,7 +27,6 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerDropItemEvent; @@ -47,19 +36,16 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; -import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerShearEntityEvent; -import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; @@ -69,7 +55,7 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAU /** * Listener class for player events. */ -public class AuthMePlayerListener implements Listener, Reloadable { +public class AuthMePlayerListener implements Listener { public static final ConcurrentHashMap joinMessage = new ConcurrentHashMap<>(); @@ -94,8 +80,6 @@ public class AuthMePlayerListener implements Listener, Reloadable { @Inject private PermissionsManager permissionsManager; - private Pattern nicknamePattern; - private void sendLoginOrRegisterMessage(final Player player) { bukkitService.runTaskAsynchronously(new Runnable() { @Override @@ -231,151 +215,6 @@ public class AuthMePlayerListener implements Listener, Reloadable { } } - @EventHandler(priority = EventPriority.LOW) - public void onPlayerJoin(PlayerJoinEvent event) { - final Player player = event.getPlayer(); - if (player == null) { - return; - } - - // Schedule login task so works after the prelogin - // (Fix found by Koolaid5000) - bukkitService.runTask(new Runnable() { - @Override - public void run() { - management.performJoin(player); - } - }); - } - - // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode - @EventHandler(priority = EventPriority.HIGHEST) - public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = dataSource.getAuth(event.getName()); - if (auth == null && antiBot.getAntiBotStatus() == AntiBotStatus.ACTIVE) { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - antiBot.antibotKicked.addIfAbsent(event.getName()); - return; - } - if (auth == null && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { - event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - return; - } - final String name = event.getName().toLowerCase(); - if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - return; - } - if (settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE) && auth != null && auth.getRealName() != null) { - String realName = auth.getRealName(); - if (!realName.isEmpty() && !"Player".equals(realName) && !realName.equals(event.getName())) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CASE, realName, event.getName())); - return; - } - if (realName.isEmpty() || "Player".equals(realName)) { - dataSource.updateRealName(event.getName().toLowerCase(), event.getName()); - } - } - - if (auth == null && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { - String playerIp = event.getAddress().getHostAddress(); - if (!validationService.isCountryAdmitted(playerIp)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } - } - - final Player player = bukkitService.getPlayerExact(name); - // Check if forceSingleSession is set to true, so kick player that has - // joined with same nick of online player - if (player != null && settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR)); - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); - if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { - Utils.addNormal(player, limbo.getGroup()); - LimboCache.getInstance().deleteLimboPlayer(name); - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerLogin(PlayerLoginEvent event) { - final Player player = event.getPlayer(); - if (player == null || Utils.isUnrestricted(player)) { - return; - } - - if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) { - if (permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { - int playersOnline = bukkitService.getOnlinePlayers().size(); - if (playersOnline > plugin.getServer().getMaxPlayers()) { - event.allow(); - } else { - Player pl = plugin.generateKickPlayer(bukkitService.getOnlinePlayers()); - if (pl != null) { - pl.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); - event.allow(); - } else { - ConsoleLogger.info("The player " + event.getPlayer().getName() + " tried to join, but the server was full"); - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - } - } - } else { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - return; - } - } - - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { - return; - } - - final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = dataSource.isAuthAvailable(name); - - if (antiBot.getAntiBotStatus() == AntiBotStatus.ACTIVE && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - antiBot.antibotKicked.addIfAbsent(player.getName()); - return; - } - - if (settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED) && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - if (name.equalsIgnoreCase("Player") || !nicknamePattern.matcher(player.getName()).matches()) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS) - .replace("REG_EX", nicknamePattern.pattern())); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - antiBot.checkAntiBot(player); - - if (settings.getProperty(HooksSettings.BUNGEECORD)) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("IP"); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - } - @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); @@ -548,16 +387,4 @@ public class AuthMePlayerListener implements Listener, Reloadable { } } - @PostConstruct - @Override - public void reload() { - String nickRegEx = settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS); - try { - nicknamePattern = Pattern.compile(nickRegEx); - } catch (Exception e) { - nicknamePattern = Pattern.compile(".*?"); - ConsoleLogger.showError("Nickname pattern is not a valid regular expression! " - + "Fallback to allowing all nicknames"); - } - } } diff --git a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java index 4749dac4..d3503d4d 100644 --- a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java @@ -21,11 +21,11 @@ import static org.junit.Assert.fail; public final class ListenerConsistencyTest { private static final Class[] LISTENERS = { AuthMeBlockListener.class, AuthMeEntityListener.class, - AuthMePlayerListener.class, AuthMePlayerListener16.class, AuthMePlayerListener18.class, - AuthMeServerListener.class }; + AuthMePlayerJoinListener.class, AuthMePlayerListener.class, AuthMePlayerListener16.class, + AuthMePlayerListener18.class, AuthMeServerListener.class }; - private static final Set CANCELED_EXCEPTIONS = Sets.newHashSet("AuthMePlayerListener#onPlayerJoin", - "AuthMePlayerListener#onPreLogin", "AuthMePlayerListener#onPlayerLogin", + private static final Set CANCELED_EXCEPTIONS = Sets.newHashSet("AuthMePlayerJoinListener#onPlayerJoin", + "AuthMePlayerJoinListener#onPreLogin", "AuthMePlayerJoinListener#onPlayerLogin", "AuthMePlayerListener#onPlayerQuit", "AuthMeServerListener#onPluginDisable", "AuthMeServerListener#onServerPing", "AuthMeServerListener#onPluginEnable", "AuthMePlayerListener#onJoinMessage"); From 39d8a88142af971a7eecdc7019d1c2895dd8eefe Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 May 2016 11:45:12 +0200 Subject: [PATCH 2/9] Structure checks done in PlayerJoinListener as individual methods --- src/main/java/fr/xephi/authme/AntiBot.java | 11 +- .../listener/AuthMePlayerJoinListener.java | 312 +++++++++++------- .../authme/listener/AuthMePlayerListener.java | 17 +- .../java/fr/xephi/authme/AntiBotTest.java | 4 +- 4 files changed, 216 insertions(+), 128 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AntiBot.java b/src/main/java/fr/xephi/authme/AntiBot.java index 071842bc..d85cc784 100644 --- a/src/main/java/fr/xephi/authme/AntiBot.java +++ b/src/main/java/fr/xephi/authme/AntiBot.java @@ -29,8 +29,8 @@ public class AntiBot { private AntiBotStatus antiBotStatus = AntiBotStatus.DISABLED; @Inject - public AntiBot(NewSetting settings, Messages messages, PermissionsManager permissionsManager, - BukkitService bukkitService) { + AntiBot(NewSetting settings, Messages messages, PermissionsManager permissionsManager, + BukkitService bukkitService) { this.settings = settings; this.messages = messages; this.permissionsManager = permissionsManager; @@ -86,7 +86,12 @@ public class AntiBot { }, duration * TICKS_PER_MINUTE); } - public void checkAntiBot(final Player player) { + /** + * Handles a player joining the server and checks if AntiBot needs to be activated. + * + * @param player the player who joined the server + */ + public void handlePlayerJoin(final Player player) { if (antiBotStatus == AntiBotStatus.ACTIVE || antiBotStatus == AntiBotStatus.DISABLED) { return; } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java index c83d77a5..5343f214 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java @@ -22,6 +22,7 @@ import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.ValidationService; import org.bukkit.entity.Player; @@ -60,79 +61,42 @@ public class AuthMePlayerJoinListener implements Listener, Reloadable { private ValidationService validationService; @Inject private AuthMe plugin; + @Inject + private LimboCache limboCache; private Pattern nicknamePattern; @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - if (player == null) { - return; + if (player != null) { + // Schedule login task so works after the prelogin + // (Fix found by Koolaid5000) + bukkitService.runTask(new Runnable() { + @Override + public void run() { + management.performJoin(player); + } + }); } - - // Schedule login task so works after the prelogin - // (Fix found by Koolaid5000) - bukkitService.runTask(new Runnable() { - @Override - public void run() { - management.performJoin(player); - } - }); } // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode + // e.g. CraftBukkit does not. So we need to run crucial things in onPlayerLogin, too @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = dataSource.getAuth(event.getName()); - if (auth == null && antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE) { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - antiBot.antibotKicked.addIfAbsent(event.getName()); - return; - } - if (auth == null && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { - event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - return; - } final String name = event.getName().toLowerCase(); - if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - return; - } - if (settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE) && auth != null && auth.getRealName() != null) { - String realName = auth.getRealName(); - if (!realName.isEmpty() && !"Player".equals(realName) && !realName.equals(event.getName())) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CASE, realName, event.getName())); - return; - } - if (realName.isEmpty() || "Player".equals(realName)) { - dataSource.updateRealName(event.getName().toLowerCase(), event.getName()); - } - } + final boolean isAuthAvailable = dataSource.isAuthAvailable(event.getName()); - if (auth == null && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { - String playerIp = event.getAddress().getHostAddress(); - if (!validationService.isCountryAdmitted(playerIp)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } - } - - final Player player = bukkitService.getPlayerExact(name); - // Check if forceSingleSession is set to true, so kick player that has - // joined with same nick of online player - if (player != null && settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + try { + // Potential performance improvement: make checkAntiBot not require `isAuthAvailable` info and use + // "checkKickNonRegistered" as last -> no need to query the DB before checking antibot / name + checkAntibot(name, isAuthAvailable); + checkKickNonRegistered(isAuthAvailable); + checkIsValidName(name); + } catch (VerificationFailedException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR)); - LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); - if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { - Utils.addNormal(player, limbo.getGroup()); - LimboCache.getInstance().deleteLimboPlayer(name); - } } } @@ -141,65 +105,30 @@ public class AuthMePlayerJoinListener implements Listener, Reloadable { final Player player = event.getPlayer(); if (player == null || Utils.isUnrestricted(player)) { return; - } - - if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) { - if (permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { - int playersOnline = bukkitService.getOnlinePlayers().size(); - if (playersOnline > plugin.getServer().getMaxPlayers()) { - event.allow(); - } else { - Player pl = generateKickPlayer(bukkitService.getOnlinePlayers()); - if (pl != null) { - pl.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); - event.allow(); - } else { - ConsoleLogger.info("The player " + event.getPlayer().getName() + " tried to join, but the server was full"); - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - } - } - } else { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - event.setResult(PlayerLoginEvent.Result.KICK_FULL); - return; - } - } - - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + } else if (refusePlayerForFullServer(event)) { + return; + } else if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { return; } final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = dataSource.isAuthAvailable(name); + final PlayerAuth auth = dataSource.getAuth(player.getName()); + final boolean isAuthAvailable = auth != null; - if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_ANTIBOT)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - antiBot.antibotKicked.addIfAbsent(player.getName()); - return; - } - - if (settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED) && !isAuthAvailable) { - event.setKickMessage(m.retrieveSingle(MessageKey.MUST_REGISTER_MESSAGE)); + try { + checkAntibot(name, isAuthAvailable); + checkKickNonRegistered(isAuthAvailable); + checkIsValidName(name); + checkNameCasing(player, auth); + checkSingleSession(player); + checkPlayerCountry(isAuthAvailable, event); + } catch (VerificationFailedException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); event.setResult(PlayerLoginEvent.Result.KICK_OTHER); return; } - if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_LENGTH)); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - if (name.equalsIgnoreCase("Player") || !nicknamePattern.matcher(player.getName()).matches()) { - event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CHARACTERS) - .replace("REG_EX", nicknamePattern.pattern())); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - antiBot.checkAntiBot(player); + antiBot.handlePlayerJoin(player); if (settings.getProperty(HooksSettings.BUNGEECORD)) { ByteArrayDataOutput out = ByteStreams.newDataOutput(); @@ -221,13 +150,174 @@ public class AuthMePlayerJoinListener implements Listener, Reloadable { } } - // Select the player to kick when a vip player joins the server when full - private Player generateKickPlayer(Collection collection) { - for (Player player : collection) { + /** + * Selects a non-VIP player to kick when a VIP player joins the server when full. + * + * @param onlinePlayers list of online players + * @return the player to kick, or null if none applicable + */ + private Player generateKickPlayer(Collection onlinePlayers) { + for (Player player : onlinePlayers) { if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { return player; } } return null; } + + /** + * Checks if Antibot is enabled. + * + * @param playerName the name of the player (lowercase) + * @param isAuthAvailable whether or not the player is registered + */ + private void checkAntibot(String playerName, boolean isAuthAvailable) throws VerificationFailedException { + if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { + antiBot.antibotKicked.addIfAbsent(playerName); + throw new VerificationFailedException(MessageKey.KICK_ANTIBOT); + } + } + + /** + * Checks whether non-registered players should be kicked, and if so, whether the player should be kicked. + * + * @param isAuthAvailable whether or not the player is registered + */ + private void checkKickNonRegistered(boolean isAuthAvailable) throws VerificationFailedException { + if (!isAuthAvailable && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { + throw new VerificationFailedException(MessageKey.MUST_REGISTER_MESSAGE); + } + } + + /** + * Checks that the name adheres to the configured username restrictions. + * + * @param name the name to verify + */ + private void checkIsValidName(String name) throws VerificationFailedException { + if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) + || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { + throw new VerificationFailedException(MessageKey.INVALID_NAME_LENGTH); + } + if (!nicknamePattern.matcher(name).matches()) { + throw new VerificationFailedException(MessageKey.INVALID_NAME_CHARACTERS, nicknamePattern.pattern()); + } + } + + /** + * Handles the case of a full server and verifies if the user's connection should really be refused + * by adjusting the event object accordingly. Attempts to kick a non-VIP player to make room if the + * joining player is a VIP. + * + * @param event the login event to verify + * @return true if the player's connection should be refused (i.e. the event does not need to be processed + * further), false if the player is not refused + */ + private boolean refusePlayerForFullServer(PlayerLoginEvent event) { + final Player player = event.getPlayer(); + if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) { + // Server is not full, no need to do anything + return false; + } else if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + // Server is full and player is NOT VIP; set kick message and proceed with kick + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + + // Server is full and player is VIP; attempt to kick a non-VIP player to make room + Collection onlinePlayers = bukkitService.getOnlinePlayers(); + if (onlinePlayers.size() < plugin.getServer().getMaxPlayers()) { + event.allow(); + return false; + } + Player nonVipPlayer = generateKickPlayer(onlinePlayers); + if (nonVipPlayer != null) { + nonVipPlayer.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); + event.allow(); + return false; + } else { + ConsoleLogger.info("VIP player " + player.getName() + " tried to join, but the server was full"); + event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + } + + /** + * Checks that the casing in the username corresponds to the one in the database, if so configured. + * + * @param player the player to verify + * @param auth the auth object associated with the player + */ + private void checkNameCasing(Player player, PlayerAuth auth) throws VerificationFailedException { + if (auth != null && settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)) { + String realName = auth.getRealName(); // might be null or "Player" + String connectingName = player.getName(); + + if (StringUtils.isEmpty(realName) || "Player".equals(realName)) { + dataSource.updateRealName(connectingName.toLowerCase(), connectingName); + } else if (!realName.equals(connectingName)) { + throw new VerificationFailedException(MessageKey.INVALID_NAME_CASE, realName, connectingName); + } + } + } + + /** + * Checks that the player's country is admitted if he is not registered. + * + * @param isAuthAvailable whether or not the user is registered + * @param event the login event of the player + */ + private void checkPlayerCountry(boolean isAuthAvailable, + PlayerLoginEvent event) throws VerificationFailedException { + if (!isAuthAvailable && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { + throw new VerificationFailedException(MessageKey.COUNTRY_BANNED_ERROR); + } + } + } + + /** + * Checks if a player with the same name (case-insensitive) is already playing and refuses the + * connection if so configured. + * + * @param player the player to verify + */ + private void checkSingleSession(Player player) throws VerificationFailedException { + if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + return; + } + + Player onlinePlayer = bukkitService.getPlayerExact(player.getName()); + if (onlinePlayer != null) { + String name = player.getName().toLowerCase(); + LimboPlayer limbo = limboCache.getLimboPlayer(name); + if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { + Utils.addNormal(player, limbo.getGroup()); + limboCache.deleteLimboPlayer(name); + } + throw new VerificationFailedException(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); + } + } + + /** + * Exception thrown when a verification has failed and the player should be kicked. + */ + private static final class VerificationFailedException extends Exception { + private final MessageKey reason; + private final String[] args; + + public VerificationFailedException(MessageKey reason, String... args) { + this.reason = reason; + this.args = args; + } + + public MessageKey getReason() { + return reason; + } + + public String[] getArgs() { + return args; + } + } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index a928b915..ae3b49c4 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,12 +1,11 @@ package fr.xephi.authme.listener; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.SpawnLoader; @@ -15,8 +14,6 @@ import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.ValidationService; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -59,8 +56,6 @@ public class AuthMePlayerListener implements Listener { public static final ConcurrentHashMap joinMessage = new ConcurrentHashMap<>(); - @Inject - private AuthMe plugin; @Inject private NewSetting settings; @Inject @@ -76,9 +71,7 @@ public class AuthMePlayerListener implements Listener { @Inject private SpawnLoader spawnLoader; @Inject - private ValidationService validationService; - @Inject - private PermissionsManager permissionsManager; + private PluginHooks pluginHooks; private void sendLoginOrRegisterMessage(final Player player) { bukkitService.runTaskAsynchronously(new Runnable() { @@ -249,7 +242,7 @@ public class AuthMePlayerListener implements Listener { } if (!antiBot.antibotKicked.contains(player.getName())) { - plugin.getManagement().performQuit(player, true); + management.performQuit(player, true); } } @@ -287,7 +280,7 @@ public class AuthMePlayerListener implements Listener { * @note little hack cause InventoryOpenEvent cannot be cancelled for * real, cause no packet is send to server by client for the main inv */ - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + bukkitService.scheduleSyncDelayedTask(new Runnable() { @Override public void run() { player.closeInventory(); @@ -307,7 +300,7 @@ public class AuthMePlayerListener implements Listener { if (Utils.checkAuth(player)) { return; } - if (plugin.getPluginHooks().isNpc(player)) { + if (pluginHooks.isNpc(player)) { return; } event.setCancelled(true); diff --git a/src/test/java/fr/xephi/authme/AntiBotTest.java b/src/test/java/fr/xephi/authme/AntiBotTest.java index 9950963a..ddf41831 100644 --- a/src/test/java/fr/xephi/authme/AntiBotTest.java +++ b/src/test/java/fr/xephi/authme/AntiBotTest.java @@ -164,7 +164,7 @@ public class AntiBotTest { AntiBot antiBot = createListeningAntiBot(); // when - antiBot.checkAntiBot(player); + antiBot.handlePlayerJoin(player); // then @SuppressWarnings("unchecked") @@ -194,7 +194,7 @@ public class AntiBotTest { AntiBot antiBot = createListeningAntiBot(); // when - antiBot.checkAntiBot(player); + antiBot.handlePlayerJoin(player); // then @SuppressWarnings("rawtypes") From 64aacb12dbd7d16707ecdf92c5330a0d23a90d40 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 May 2016 15:00:16 +0200 Subject: [PATCH 3/9] Unit test verifications done on joining - Write unit tests for checks done when a player joins - Move join event handler methods back to PlayerListener; move join check logic to new separate class --- src/main/java/fr/xephi/authme/AuthMe.java | 2 - .../authme/listener/AuthMePlayerListener.java | 80 ++++ .../listener/FailedVerificationException.java | 32 ++ ...rJoinListener.java => OnJoinVerifier.java} | 394 ++++++---------- .../listener/ListenerConsistencyTest.java | 8 +- .../authme/listener/OnJoinVerifierTest.java | 419 ++++++++++++++++++ 6 files changed, 682 insertions(+), 253 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/listener/FailedVerificationException.java rename src/main/java/fr/xephi/authme/listener/{AuthMePlayerJoinListener.java => OnJoinVerifier.java} (56%) create mode 100644 src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 98f6cca2..9c2124d7 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -22,7 +22,6 @@ import fr.xephi.authme.initialization.MetricsStarter; import fr.xephi.authme.listener.AuthMeBlockListener; import fr.xephi.authme.listener.AuthMeEntityListener; import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; -import fr.xephi.authme.listener.AuthMePlayerJoinListener; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.listener.AuthMePlayerListener16; import fr.xephi.authme.listener.AuthMePlayerListener18; @@ -360,7 +359,6 @@ public class AuthMe extends JavaPlugin { pluginManager.registerEvents(initializer.get(AuthMeBlockListener.class), this); pluginManager.registerEvents(initializer.get(AuthMeEntityListener.class), this); pluginManager.registerEvents(initializer.get(AuthMeServerListener.class), this); - pluginManager.registerEvents(initializer.get(AuthMePlayerJoinListener.class), this); // Try to register 1.6 player listeners try { diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index ae3b49c4..ce882bcb 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,6 +1,9 @@ package fr.xephi.authme.listener; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; import fr.xephi.authme.AntiBot; +import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.hooks.PluginHooks; @@ -24,6 +27,7 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerDropItemEvent; @@ -33,6 +37,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerQuitEvent; @@ -72,6 +77,10 @@ public class AuthMePlayerListener implements Listener { private SpawnLoader spawnLoader; @Inject private PluginHooks pluginHooks; + @Inject + private OnJoinVerifier onJoinVerifier; + @Inject + private AuthMe plugin; private void sendLoginOrRegisterMessage(final Player player) { bukkitService.runTaskAsynchronously(new Runnable() { @@ -208,6 +217,77 @@ public class AuthMePlayerListener implements Listener { } } + @EventHandler(priority = EventPriority.LOW) + public void onPlayerJoin(PlayerJoinEvent event) { + final Player player = event.getPlayer(); + if (player != null) { + // Schedule login task so works after the prelogin + // (Fix found by Koolaid5000) + bukkitService.runTask(new Runnable() { + @Override + public void run() { + management.performJoin(player); + } + }); + } + } + + // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode + // e.g. CraftBukkit does not. So we need to run crucial things in onPlayerLogin, too + @EventHandler(priority = EventPriority.HIGHEST) + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + final String name = event.getName().toLowerCase(); + final boolean isAuthAvailable = dataSource.isAuthAvailable(event.getName()); + + try { + // Potential performance improvement: make checkAntiBot not require `isAuthAvailable` info and use + // "checkKickNonRegistered" as last -> no need to query the DB before checking antibot / name + onJoinVerifier.checkAntibot(name, isAuthAvailable); + onJoinVerifier.checkKickNonRegistered(isAuthAvailable); + onJoinVerifier.checkIsValidName(name); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerLogin(PlayerLoginEvent event) { + final Player player = event.getPlayer(); + if (player == null || Utils.isUnrestricted(player)) { + return; + } else if (onJoinVerifier.refusePlayerForFullServer(event)) { + return; + } else if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + return; + } + + final String name = player.getName().toLowerCase(); + final PlayerAuth auth = dataSource.getAuth(player.getName()); + final boolean isAuthAvailable = auth != null; + + try { + onJoinVerifier.checkAntibot(name, isAuthAvailable); + onJoinVerifier.checkKickNonRegistered(isAuthAvailable); + onJoinVerifier.checkIsValidName(name); + onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkSingleSession(player); + onJoinVerifier.checkPlayerCountry(isAuthAvailable, event); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + return; + } + + antiBot.handlePlayerJoin(player); + + if (settings.getProperty(HooksSettings.BUNGEECORD)) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("IP"); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + } + @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); diff --git a/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java new file mode 100644 index 00000000..e560ca7f --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java @@ -0,0 +1,32 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.util.StringUtils; + +/** + * Exception thrown when a verification has failed. + */ +public class FailedVerificationException extends Exception { + + private final MessageKey reason; + private final String[] args; + + public FailedVerificationException(MessageKey reason, String... args) { + this.reason = reason; + this.args = args; + } + + public MessageKey getReason() { + return reason; + } + + public String[] getArgs() { + return args; + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": reason=" + (reason == null ? "null" : reason) + + ";args=" + (args == null ? "null" : StringUtils.join(", ", args)); + } +} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java similarity index 56% rename from src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java rename to src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index 5343f214..64f8c3f7 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerJoinListener.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -1,9 +1,6 @@ package fr.xephi.authme.listener; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -15,9 +12,7 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.NewSetting; -import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -25,12 +20,8 @@ import fr.xephi.authme.util.BukkitService; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.ValidationService; +import org.bukkit.Server; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerLoginEvent; import javax.annotation.PostConstruct; @@ -39,103 +30,33 @@ import java.util.Collection; import java.util.regex.Pattern; /** - * Listener for player join events. + * Service for performing various verifications when a player joins. */ -public class AuthMePlayerJoinListener implements Listener, Reloadable { +class OnJoinVerifier implements Reloadable { - @Inject - private BukkitService bukkitService; - @Inject - private DataSource dataSource; - @Inject - private AntiBot antiBot; - @Inject - private Management management; @Inject private NewSetting settings; @Inject - private Messages m; + private DataSource dataSource; + @Inject + private Messages messages; @Inject private PermissionsManager permissionsManager; @Inject + private AntiBot antiBot; + @Inject private ValidationService validationService; @Inject - private AuthMe plugin; + private BukkitService bukkitService; @Inject private LimboCache limboCache; + @Inject + private Server server; private Pattern nicknamePattern; - @EventHandler(priority = EventPriority.LOW) - public void onPlayerJoin(PlayerJoinEvent event) { - final Player player = event.getPlayer(); - if (player != null) { - // Schedule login task so works after the prelogin - // (Fix found by Koolaid5000) - bukkitService.runTask(new Runnable() { - @Override - public void run() { - management.performJoin(player); - } - }); - } - } + OnJoinVerifier() { } - // Note ljacqu 20160528: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode - // e.g. CraftBukkit does not. So we need to run crucial things in onPlayerLogin, too - @EventHandler(priority = EventPriority.HIGHEST) - public void onPreLogin(AsyncPlayerPreLoginEvent event) { - final String name = event.getName().toLowerCase(); - final boolean isAuthAvailable = dataSource.isAuthAvailable(event.getName()); - - try { - // Potential performance improvement: make checkAntiBot not require `isAuthAvailable` info and use - // "checkKickNonRegistered" as last -> no need to query the DB before checking antibot / name - checkAntibot(name, isAuthAvailable); - checkKickNonRegistered(isAuthAvailable); - checkIsValidName(name); - } catch (VerificationFailedException e) { - event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerLogin(PlayerLoginEvent event) { - final Player player = event.getPlayer(); - if (player == null || Utils.isUnrestricted(player)) { - return; - } else if (refusePlayerForFullServer(event)) { - return; - } else if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { - return; - } - - final String name = player.getName().toLowerCase(); - final PlayerAuth auth = dataSource.getAuth(player.getName()); - final boolean isAuthAvailable = auth != null; - - try { - checkAntibot(name, isAuthAvailable); - checkKickNonRegistered(isAuthAvailable); - checkIsValidName(name); - checkNameCasing(player, auth); - checkSingleSession(player); - checkPlayerCountry(isAuthAvailable, event); - } catch (VerificationFailedException e) { - event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - - antiBot.handlePlayerJoin(player); - - if (settings.getProperty(HooksSettings.BUNGEECORD)) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("IP"); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - } @PostConstruct @Override @@ -150,6 +71,141 @@ public class AuthMePlayerJoinListener implements Listener, Reloadable { } } + /** + * Checks if Antibot is enabled. + * + * @param playerName the name of the player (lowercase) + * @param isAuthAvailable whether or not the player is registered + */ + public void checkAntibot(String playerName, boolean isAuthAvailable) throws FailedVerificationException { + if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { + antiBot.antibotKicked.addIfAbsent(playerName); + throw new FailedVerificationException(MessageKey.KICK_ANTIBOT); + } + } + + /** + * Checks whether non-registered players should be kicked, and if so, whether the player should be kicked. + * + * @param isAuthAvailable whether or not the player is registered + */ + public void checkKickNonRegistered(boolean isAuthAvailable) throws FailedVerificationException { + if (!isAuthAvailable && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { + throw new FailedVerificationException(MessageKey.MUST_REGISTER_MESSAGE); + } + } + + /** + * Checks that the name adheres to the configured username restrictions. + * + * @param name the name to verify + */ + public void checkIsValidName(String name) throws FailedVerificationException { + if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) + || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_LENGTH); + } + if (!nicknamePattern.matcher(name).matches()) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_CHARACTERS, nicknamePattern.pattern()); + } + } + + /** + * Handles the case of a full server and verifies if the user's connection should really be refused + * by adjusting the event object accordingly. Attempts to kick a non-VIP player to make room if the + * joining player is a VIP. + * + * @param event the login event to verify + * @return true if the player's connection should be refused (i.e. the event does not need to be processed + * further), false if the player is not refused + */ + public boolean refusePlayerForFullServer(PlayerLoginEvent event) { + final Player player = event.getPlayer(); + if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) { + // Server is not full, no need to do anything + return false; + } else if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { + // Server is full and player is NOT VIP; set kick message and proceed with kick + event.setKickMessage(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + + // Server is full and player is VIP; attempt to kick a non-VIP player to make room + Collection onlinePlayers = bukkitService.getOnlinePlayers(); + if (onlinePlayers.size() < server.getMaxPlayers()) { + event.allow(); + return false; + } + Player nonVipPlayer = generateKickPlayer(onlinePlayers); + if (nonVipPlayer != null) { + nonVipPlayer.kickPlayer(messages.retrieveSingle(MessageKey.KICK_FOR_VIP)); + event.allow(); + return false; + } else { + ConsoleLogger.info("VIP player " + player.getName() + " tried to join, but the server was full"); + event.setKickMessage(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)); + return true; + } + } + + /** + * Checks that the casing in the username corresponds to the one in the database, if so configured. + * + * @param player the player to verify + * @param auth the auth object associated with the player + */ + public void checkNameCasing(Player player, PlayerAuth auth) throws FailedVerificationException { + if (auth != null && settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)) { + String realName = auth.getRealName(); // might be null or "Player" + String connectingName = player.getName(); + + if (StringUtils.isEmpty(realName) || "Player".equals(realName)) { + dataSource.updateRealName(connectingName.toLowerCase(), connectingName); + } else if (!realName.equals(connectingName)) { + throw new FailedVerificationException(MessageKey.INVALID_NAME_CASE, realName, connectingName); + } + } + } + + /** + * Checks that the player's country is admitted if he is not registered. + * + * @param isAuthAvailable whether or not the user is registered + * @param event the login event of the player + */ + public void checkPlayerCountry(boolean isAuthAvailable, + PlayerLoginEvent event) throws FailedVerificationException { + if (!isAuthAvailable && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { + throw new FailedVerificationException(MessageKey.COUNTRY_BANNED_ERROR); + } + } + } + + /** + * Checks if a player with the same name (case-insensitive) is already playing and refuses the + * connection if so configured. + * + * @param player the player to verify + */ + public void checkSingleSession(Player player) throws FailedVerificationException { + if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { + return; + } + + Player onlinePlayer = bukkitService.getPlayerExact(player.getName()); + if (onlinePlayer != null) { + String name = player.getName().toLowerCase(); + LimboPlayer limbo = limboCache.getLimboPlayer(name); + if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { + Utils.addNormal(player, limbo.getGroup()); + limboCache.deleteLimboPlayer(name); + } + throw new FailedVerificationException(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); + } + } + /** * Selects a non-VIP player to kick when a VIP player joins the server when full. * @@ -164,160 +220,4 @@ public class AuthMePlayerJoinListener implements Listener, Reloadable { } return null; } - - /** - * Checks if Antibot is enabled. - * - * @param playerName the name of the player (lowercase) - * @param isAuthAvailable whether or not the player is registered - */ - private void checkAntibot(String playerName, boolean isAuthAvailable) throws VerificationFailedException { - if (antiBot.getAntiBotStatus() == AntiBot.AntiBotStatus.ACTIVE && !isAuthAvailable) { - antiBot.antibotKicked.addIfAbsent(playerName); - throw new VerificationFailedException(MessageKey.KICK_ANTIBOT); - } - } - - /** - * Checks whether non-registered players should be kicked, and if so, whether the player should be kicked. - * - * @param isAuthAvailable whether or not the player is registered - */ - private void checkKickNonRegistered(boolean isAuthAvailable) throws VerificationFailedException { - if (!isAuthAvailable && settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)) { - throw new VerificationFailedException(MessageKey.MUST_REGISTER_MESSAGE); - } - } - - /** - * Checks that the name adheres to the configured username restrictions. - * - * @param name the name to verify - */ - private void checkIsValidName(String name) throws VerificationFailedException { - if (name.length() > settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH) - || name.length() < settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)) { - throw new VerificationFailedException(MessageKey.INVALID_NAME_LENGTH); - } - if (!nicknamePattern.matcher(name).matches()) { - throw new VerificationFailedException(MessageKey.INVALID_NAME_CHARACTERS, nicknamePattern.pattern()); - } - } - - /** - * Handles the case of a full server and verifies if the user's connection should really be refused - * by adjusting the event object accordingly. Attempts to kick a non-VIP player to make room if the - * joining player is a VIP. - * - * @param event the login event to verify - * @return true if the player's connection should be refused (i.e. the event does not need to be processed - * further), false if the player is not refused - */ - private boolean refusePlayerForFullServer(PlayerLoginEvent event) { - final Player player = event.getPlayer(); - if (event.getResult() != PlayerLoginEvent.Result.KICK_FULL) { - // Server is not full, no need to do anything - return false; - } else if (!permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)) { - // Server is full and player is NOT VIP; set kick message and proceed with kick - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - return true; - } - - // Server is full and player is VIP; attempt to kick a non-VIP player to make room - Collection onlinePlayers = bukkitService.getOnlinePlayers(); - if (onlinePlayers.size() < plugin.getServer().getMaxPlayers()) { - event.allow(); - return false; - } - Player nonVipPlayer = generateKickPlayer(onlinePlayers); - if (nonVipPlayer != null) { - nonVipPlayer.kickPlayer(m.retrieveSingle(MessageKey.KICK_FOR_VIP)); - event.allow(); - return false; - } else { - ConsoleLogger.info("VIP player " + player.getName() + " tried to join, but the server was full"); - event.setKickMessage(m.retrieveSingle(MessageKey.KICK_FULL_SERVER)); - return true; - } - } - - /** - * Checks that the casing in the username corresponds to the one in the database, if so configured. - * - * @param player the player to verify - * @param auth the auth object associated with the player - */ - private void checkNameCasing(Player player, PlayerAuth auth) throws VerificationFailedException { - if (auth != null && settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)) { - String realName = auth.getRealName(); // might be null or "Player" - String connectingName = player.getName(); - - if (StringUtils.isEmpty(realName) || "Player".equals(realName)) { - dataSource.updateRealName(connectingName.toLowerCase(), connectingName); - } else if (!realName.equals(connectingName)) { - throw new VerificationFailedException(MessageKey.INVALID_NAME_CASE, realName, connectingName); - } - } - } - - /** - * Checks that the player's country is admitted if he is not registered. - * - * @param isAuthAvailable whether or not the user is registered - * @param event the login event of the player - */ - private void checkPlayerCountry(boolean isAuthAvailable, - PlayerLoginEvent event) throws VerificationFailedException { - if (!isAuthAvailable && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { - String playerIp = event.getAddress().getHostAddress(); - if (!validationService.isCountryAdmitted(playerIp)) { - throw new VerificationFailedException(MessageKey.COUNTRY_BANNED_ERROR); - } - } - } - - /** - * Checks if a player with the same name (case-insensitive) is already playing and refuses the - * connection if so configured. - * - * @param player the player to verify - */ - private void checkSingleSession(Player player) throws VerificationFailedException { - if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)) { - return; - } - - Player onlinePlayer = bukkitService.getPlayerExact(player.getName()); - if (onlinePlayer != null) { - String name = player.getName().toLowerCase(); - LimboPlayer limbo = limboCache.getLimboPlayer(name); - if (limbo != null && PlayerCache.getInstance().isAuthenticated(name)) { - Utils.addNormal(player, limbo.getGroup()); - limboCache.deleteLimboPlayer(name); - } - throw new VerificationFailedException(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); - } - } - - /** - * Exception thrown when a verification has failed and the player should be kicked. - */ - private static final class VerificationFailedException extends Exception { - private final MessageKey reason; - private final String[] args; - - public VerificationFailedException(MessageKey reason, String... args) { - this.reason = reason; - this.args = args; - } - - public MessageKey getReason() { - return reason; - } - - public String[] getArgs() { - return args; - } - } } diff --git a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java index d3503d4d..4749dac4 100644 --- a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java @@ -21,11 +21,11 @@ import static org.junit.Assert.fail; public final class ListenerConsistencyTest { private static final Class[] LISTENERS = { AuthMeBlockListener.class, AuthMeEntityListener.class, - AuthMePlayerJoinListener.class, AuthMePlayerListener.class, AuthMePlayerListener16.class, - AuthMePlayerListener18.class, AuthMeServerListener.class }; + AuthMePlayerListener.class, AuthMePlayerListener16.class, AuthMePlayerListener18.class, + AuthMeServerListener.class }; - private static final Set CANCELED_EXCEPTIONS = Sets.newHashSet("AuthMePlayerJoinListener#onPlayerJoin", - "AuthMePlayerJoinListener#onPreLogin", "AuthMePlayerJoinListener#onPlayerLogin", + private static final Set CANCELED_EXCEPTIONS = Sets.newHashSet("AuthMePlayerListener#onPlayerJoin", + "AuthMePlayerListener#onPreLogin", "AuthMePlayerListener#onPlayerLogin", "AuthMePlayerListener#onPlayerQuit", "AuthMeServerListener#onPluginDisable", "AuthMeServerListener#onServerPing", "AuthMeServerListener#onPluginEnable", "AuthMePlayerListener#onJoinMessage"); diff --git a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java new file mode 100644 index 00000000..4f003249 --- /dev/null +++ b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java @@ -0,0 +1,419 @@ +package fr.xephi.authme.listener; + +import fr.xephi.authme.AntiBot; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.cache.limbo.LimboCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.util.BukkitService; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.ValidationService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerLoginEvent; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link OnJoinVerifier}. + */ +@RunWith(MockitoJUnitRunner.class) +public class OnJoinVerifierTest { + + @InjectMocks + private OnJoinVerifier onJoinVerifier; + + @Mock + private NewSetting settings; + @Mock + private DataSource dataSource; + @Mock + private Messages messages; + @Mock + private PermissionsManager permissionsManager; + @Mock + private AntiBot antiBot; + @Mock + private ValidationService validationService; + @Mock + private BukkitService bukkitService; + @Mock + private LimboCache limboCache; + @Mock + private Server server; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @BeforeClass + public static void setUpLogger() { + TestHelper.setupLogger(); + } + + @Test + public void shouldNotDoAnythingForNormalEvent() { + // given + PlayerLoginEvent event = mock(PlayerLoginEvent.class); + given(event.getResult()).willReturn(PlayerLoginEvent.Result.ALLOWED); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(false)); + verify(event).getResult(); + verifyNoMoreInteractions(event); + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(dataSource); + verifyZeroInteractions(permissionsManager); + } + + @Test + public void shouldRefuseNonVipPlayerForFullServer() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(false); + String serverFullMessage = "server is full"; + given(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)).willReturn(serverFullMessage); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(true)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.KICK_FULL)); + assertThat(event.getKickMessage(), equalTo(serverFullMessage)); + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldKickNonVipForJoiningVipPlayer() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(true); + List onlinePlayers = Arrays.asList(mock(Player.class), mock(Player.class)); + given(permissionsManager.hasPermission(onlinePlayers.get(0), PlayerStatePermission.IS_VIP)).willReturn(true); + given(permissionsManager.hasPermission(onlinePlayers.get(1), PlayerStatePermission.IS_VIP)).willReturn(false); + returnOnlineListFromBukkitServer(onlinePlayers); + given(server.getMaxPlayers()).willReturn(onlinePlayers.size()); + given(messages.retrieveSingle(MessageKey.KICK_FOR_VIP)).willReturn("kick for vip"); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(false)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.ALLOWED)); + // First player is VIP, so expect no interactions there and second player to have been kicked + verifyZeroInteractions(onlinePlayers.get(0)); + verify(onlinePlayers.get(1)).kickPlayer("kick for vip"); + } + + @Test + public void shouldKickVipPlayerIfNoPlayerCanBeKicked() { + // given + Player player = mock(Player.class); + PlayerLoginEvent event = new PlayerLoginEvent(player, "hostname", null); + event.setResult(PlayerLoginEvent.Result.KICK_FULL); + given(permissionsManager.hasPermission(player, PlayerStatePermission.IS_VIP)).willReturn(true); + List onlinePlayers = Collections.singletonList(mock(Player.class)); + given(permissionsManager.hasPermission(onlinePlayers.get(0), PlayerStatePermission.IS_VIP)).willReturn(true); + returnOnlineListFromBukkitServer(onlinePlayers); + given(server.getMaxPlayers()).willReturn(onlinePlayers.size()); + given(messages.retrieveSingle(MessageKey.KICK_FULL_SERVER)).willReturn("kick full server"); + + // when + boolean result = onJoinVerifier.refusePlayerForFullServer(event); + + // then + assertThat(result, equalTo(true)); + assertThat(event.getResult(), equalTo(PlayerLoginEvent.Result.KICK_FULL)); + assertThat(event.getKickMessage(), equalTo("kick full server")); + verifyZeroInteractions(onlinePlayers.get(0)); + } + + @Test + public void shouldKickNonRegistered() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.MUST_REGISTER_MESSAGE); + + // when + onJoinVerifier.checkKickNonRegistered(false); + } + + @Test + public void shouldNotKickRegisteredPlayer() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(true); + + // when + onJoinVerifier.checkKickNonRegistered(true); + } + + @Test + public void shouldNotKickUnregisteredPlayer() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.KICK_NON_REGISTERED)).willReturn(false); + + // when + onJoinVerifier.checkKickNonRegistered(false); + } + + @Test + public void shouldAllowValidName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // when + onJoinVerifier.checkIsValidName("Bobby5"); + } + + @Test + public void shouldRejectTooLongName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_LENGTH); + + // when + onJoinVerifier.checkIsValidName("longerthaneight"); + } + + @Test + public void shouldRejectTooShortName() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_LENGTH); + + // when + onJoinVerifier.checkIsValidName("abc"); + } + + @Test + public void shouldRejectNameWithInvalidCharacters() throws FailedVerificationException { + // given + given(settings.getProperty(RestrictionSettings.MIN_NICKNAME_LENGTH)).willReturn(4); + given(settings.getProperty(RestrictionSettings.MAX_NICKNAME_LENGTH)).willReturn(8); + given(settings.getProperty(RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS)).willReturn("[a-zA-Z0-9]+"); + onJoinVerifier.reload(); // @PostConstruct method + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_CHARACTERS, "[a-zA-Z0-9]+"); + + // when + onJoinVerifier.checkIsValidName("Tester!"); + } + + @Test + public void shouldAllowProperlyCasedName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Bobby"); + PlayerAuth auth = PlayerAuth.builder().name("bobby").realName("Bobby").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldRejectNameWithWrongCasing() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Tester"); + PlayerAuth auth = PlayerAuth.builder().name("tester").realName("testeR").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.INVALID_NAME_CASE, "testeR", "Tester"); + + // when / then + onJoinVerifier.checkNameCasing(player, auth); + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldUpdateMissingRealName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Authme"); + PlayerAuth auth = PlayerAuth.builder().name("authme").realName("").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verify(dataSource).updateRealName("authme", "Authme"); + } + + @Test + public void shouldUpdateDefaultRealName() throws FailedVerificationException { + // given + Player player = newPlayerWithName("SOMEONE"); + PlayerAuth auth = PlayerAuth.builder().name("someone").realName("Player").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verify(dataSource).updateRealName("someone", "SOMEONE"); + } + + @Test + public void shouldAcceptCasingMismatchForDisabledSetting() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Test"); + PlayerAuth auth = PlayerAuth.builder().name("test").realName("TEST").build(); + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(false); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAcceptNameForUnregisteredAccount() throws FailedVerificationException { + // given + Player player = newPlayerWithName("MyPlayer"); + PlayerAuth auth = null; + given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); + + // when + onJoinVerifier.checkNameCasing(player, auth); + + // then + verifyZeroInteractions(dataSource); + } + + @Test + public void shouldAcceptNameThatIsNotOnline() throws FailedVerificationException { + // given + Player player = newPlayerWithName("bobby"); + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(true); + given(bukkitService.getPlayerExact("bobby")).willReturn(null); + + // when + onJoinVerifier.checkSingleSession(player); + + // then + verifyZeroInteractions(limboCache); + } + + @Test + public void shouldRejectNameAlreadyOnline() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Charlie"); + Player onlinePlayer = newPlayerWithName("charlie"); + given(bukkitService.getPlayerExact("Charlie")).willReturn(onlinePlayer); + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(true); + + // expect + expectValidationExceptionWith(MessageKey.USERNAME_ALREADY_ONLINE_ERROR); + + // when / then + onJoinVerifier.checkSingleSession(player); + verify(limboCache).getLimboPlayer("charlie"); + } + + @Test + public void shouldAcceptAlreadyOnlineNameForDisabledSetting() throws FailedVerificationException { + // given + Player player = newPlayerWithName("Felipe"); + given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(false); + + // when + onJoinVerifier.checkSingleSession(player); + + // then + verifyZeroInteractions(bukkitService); + verifyZeroInteractions(limboCache); + } + + private static Player newPlayerWithName(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } + + @SuppressWarnings("unchecked") + private void returnOnlineListFromBukkitServer(Collection onlineList) { + // Note ljacqu 20160529: The compiler gets lost in generics because Collection is returned + // from getOnlinePlayers(). We need to uncheck onlineList to a simple Collection or it will refuse to compile. + given(bukkitService.getOnlinePlayers()).willReturn((Collection) onlineList); + } + + private void expectValidationExceptionWith(MessageKey messageKey, String... args) { + //expectedException.expect(FailedVerificationException.class); + expectedException.expect(exceptionWithData(messageKey, args)); + } + + private static Matcher exceptionWithData(final MessageKey messageKey, + final String... args) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(FailedVerificationException item) { + return messageKey.equals(item.getReason()) && Arrays.equals(args, item.getArgs()); + } + + @Override + public void describeTo(Description description) { + description.appendValue("VerificationFailedException: reason=" + messageKey + ";args=" + + (args == null ? "null" : StringUtils.join(", ", args))); + } + }; + } + +} From 52c0c7dd640217572d32587237abedb9cc35fca3 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 May 2016 16:34:03 +0200 Subject: [PATCH 4/9] playerlistener cleanup --- .../executable/authme/ConverterCommand.java | 4 -- .../authme/listener/AuthMePlayerListener.java | 39 ++++--------------- .../listener/FailedVerificationException.java | 1 + .../fr/xephi/authme/output/MessageKey.java | 2 + .../properties/RestrictionSettings.java | 6 --- src/main/resources/messages/messages_en.yml | 1 + .../authme/listener/OnJoinVerifierTest.java | 2 +- 7 files changed, 13 insertions(+), 42 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java index 4a02cfdc..5c4800ed 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.converter.Converter; @@ -23,9 +22,6 @@ import java.util.List; */ public class ConverterCommand implements ExecutableCommand { - @Inject - private AuthMe authMe; - @Inject private BukkitService bukkitService; diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index ce882bcb..9e471b85 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -51,7 +51,6 @@ import java.util.concurrent.ConcurrentHashMap; import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; -import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT; /** @@ -82,41 +81,21 @@ public class AuthMePlayerListener implements Listener { @Inject private AuthMe plugin; - private void sendLoginOrRegisterMessage(final Player player) { - bukkitService.runTaskAsynchronously(new Runnable() { - @Override - public void run() { - if (dataSource.isAuthAvailable(player.getName().toLowerCase())) { - m.send(player, MessageKey.LOGIN_MESSAGE); - } else { - if (settings.getProperty(RegistrationSettings.USE_EMAIL_REGISTRATION)) { - m.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); - } else { - m.send(player, MessageKey.REGISTER_MESSAGE); - } - } - } - }); - } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { String cmd = event.getMessage().split(" ")[0].toLowerCase(); - if (settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD) && "/motd".equals(cmd)) { - return; - } - if (!settings.getProperty(RegistrationSettings.FORCE) - && settings.getProperty(ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL)) { + if (settings.getProperty(HooksSettings.USE_ESSENTIALS_MOTD) && cmd.equals("/motd")) { return; } if (settings.getProperty(RestrictionSettings.ALLOW_COMMANDS).contains(cmd)) { return; } - if (Utils.checkAuth(event.getPlayer())) { + final Player player = event.getPlayer(); + if (!shouldCancelEvent(player)) { return; } event.setCancelled(true); - sendLoginOrRegisterMessage(event.getPlayer()); + m.send(player, MessageKey.DENIED_COMMAND); } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @@ -128,12 +107,7 @@ public class AuthMePlayerListener implements Listener { final Player player = event.getPlayer(); if (shouldCancelEvent(player)) { event.setCancelled(true); - bukkitService.runTaskAsynchronously(new Runnable() { - @Override - public void run() { - m.send(player, MessageKey.DENIED_CHAT); - } - }); + m.send(player, MessageKey.DENIED_CHAT); } else if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { Set recipients = event.getRecipients(); Iterator iter = recipients.iterator(); @@ -143,6 +117,9 @@ public class AuthMePlayerListener implements Listener { iter.remove(); } } + if (recipients.size() == 0) { + event.setCancelled(true); + } } } diff --git a/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java index e560ca7f..31957cdb 100644 --- a/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java +++ b/src/main/java/fr/xephi/authme/listener/FailedVerificationException.java @@ -6,6 +6,7 @@ import fr.xephi.authme.util.StringUtils; /** * Exception thrown when a verification has failed. */ +@SuppressWarnings("serial") public class FailedVerificationException extends Exception { private final MessageKey reason; diff --git a/src/main/java/fr/xephi/authme/output/MessageKey.java b/src/main/java/fr/xephi/authme/output/MessageKey.java index 0f052c27..eb7a5238 100644 --- a/src/main/java/fr/xephi/authme/output/MessageKey.java +++ b/src/main/java/fr/xephi/authme/output/MessageKey.java @@ -5,6 +5,8 @@ package fr.xephi.authme.output; */ public enum MessageKey { + DENIED_COMMAND("denied_command"), + SAME_IP_ONLINE("same_ip_online"), DENIED_CHAT("denied_chat"), diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index 96c790e2..926ddac7 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -22,12 +22,6 @@ public class RestrictionSettings implements SettingsClass { public static final Property HIDE_CHAT = newProperty("settings.restrictions.hideChat", false); - @Comment({ - "Allow unlogged users to use all the commands if registration is not forced!", - "WARNING: use this only if you need it!"}) - public static final Property ALLOW_ALL_COMMANDS_IF_REGISTRATION_IS_OPTIONAL = - newProperty("settings.restrictions.allowAllCommandsIfRegistrationIsOptional", false); - @Comment("Allowed commands for unauthenticated players") public static final Property> ALLOW_COMMANDS = newListProperty("settings.restrictions.allowCommands", diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 2623ac0b..75a4fcea 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -1,3 +1,4 @@ +denied_command: '&cIn order to be able to use this command you must be authenticated!' same_ip_online: 'A player with the same IP is already in game!' denied_chat: '&cIn order to be able to chat you must be authenticated!' kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' diff --git a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java index 4f003249..eb7e1511 100644 --- a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java +++ b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java @@ -388,7 +388,7 @@ public class OnJoinVerifierTest { return player; } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) private void returnOnlineListFromBukkitServer(Collection onlineList) { // Note ljacqu 20160529: The compiler gets lost in generics because Collection is returned // from getOnlinePlayers(). We need to uncheck onlineList to a simple Collection or it will refuse to compile. From f5b7246d1d6a66578032aaef33c6c8604e431d86 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 May 2016 17:10:33 +0200 Subject: [PATCH 5/9] remove useless stuff from the player listener --- .../authme/listener/AuthMePlayerListener.java | 47 ++++++------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 9e471b85..fa3b5215 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -133,18 +133,21 @@ public class AuthMePlayerListener implements Listener { * Limit player X and Z movements to 1 block * Deny player Y+ movements (allows falling) */ - if (event.getFrom().getBlockX() == event.getTo().getBlockX() - && event.getFrom().getBlockZ() == event.getTo().getBlockZ() - && event.getFrom().getY() - event.getTo().getY() >= 0) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getBlockX() == to.getBlockX() + && from.getBlockZ() == to.getBlockZ() + && from.getY() - to.getY() >= 0) { return; } Player player = event.getPlayer(); - if (Utils.checkAuth(player)) { + if (!shouldCancelEvent(player)) { return; } if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) { + // "cancel" the event event.setTo(event.getFrom()); if (settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { player.setFlySpeed(0.0f); @@ -231,7 +234,7 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerLogin(PlayerLoginEvent event) { final Player player = event.getPlayer(); - if (player == null || Utils.isUnrestricted(player)) { + if (Utils.isUnrestricted(player)) { return; } else if (onJoinVerifier.refusePlayerForFullServer(event)) { return; @@ -241,7 +244,7 @@ public class AuthMePlayerListener implements Listener { final String name = player.getName().toLowerCase(); final PlayerAuth auth = dataSource.getAuth(player.getName()); - final boolean isAuthAvailable = auth != null; + final boolean isAuthAvailable = (auth != null); try { onJoinVerifier.checkAntibot(name, isAuthAvailable); @@ -257,22 +260,12 @@ public class AuthMePlayerListener implements Listener { } antiBot.handlePlayerJoin(player); - - if (settings.getProperty(HooksSettings.BUNGEECORD)) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("IP"); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } } @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - if (player == null) { - return; - } - if (settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)) { event.setQuitMessage(null); } @@ -288,16 +281,6 @@ public class AuthMePlayerListener implements Listener { public void onPlayerKick(PlayerKickEvent event) { Player player = event.getPlayer(); - if (player == null) { - return; - } - - if (!settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION) - && event.getReason().equals(m.retrieveSingle(MessageKey.USERNAME_ALREADY_ONLINE_ERROR))) { - event.setCancelled(true); - return; - } - if (!antiBot.antibotKicked.contains(player.getName())) { management.performQuit(player, true); } @@ -328,7 +311,7 @@ public class AuthMePlayerListener implements Listener { public void onPlayerInventoryOpen(InventoryOpenEvent event) { final Player player = (Player) event.getPlayer(); - if (!ListenerService.shouldCancelEvent(player)) { + if (!shouldCancelEvent(player)) { return; } event.setCancelled(true); @@ -354,10 +337,7 @@ public class AuthMePlayerListener implements Listener { return; } Player player = (Player) event.getWhoClicked(); - if (Utils.checkAuth(player)) { - return; - } - if (pluginHooks.isNpc(player)) { + if (!shouldCancelEvent(player)) { return; } event.setCancelled(true); @@ -365,7 +345,7 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerHitPlayerEvent(EntityDamageByEntityEvent event) { - if (ListenerService.shouldCancelEvent(event)) { + if (shouldCancelEvent(event)) { event.setCancelled(true); } } @@ -394,11 +374,12 @@ public class AuthMePlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onSignChange(SignChangeEvent event) { Player player = event.getPlayer(); - if (ListenerService.shouldCancelEvent(player)) { + if (shouldCancelEvent(player)) { event.setCancelled(true); } } + // TODO: check this, why do we need to save the quit loc? @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerRespawn(PlayerRespawnEvent event) { if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { From be6ed07802e0b64d3c357c2e4b749b19d7ed9cb5 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 May 2016 17:36:36 +0200 Subject: [PATCH 6/9] Fix #568 --- .../xephi/authme/listener/AuthMeEntityListener.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java index 897607ee..1c6522c9 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java @@ -16,6 +16,9 @@ import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.projectiles.ProjectileSource; +import fr.xephi.authme.ConsoleLogger; + +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; @@ -87,7 +90,7 @@ public class AuthMeEntityListener implements Listener { } } - // TODO #568: Need to check this, player can't throw snowball but the item is taken. + // In old versions of the Bukkit API getShooter() returns a Player Object instead of a ProjectileSource @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { if (event.getEntity() == null) { @@ -103,14 +106,14 @@ public class AuthMeEntityListener implements Listener { } player = (Player) shooter; } else { - // TODO #568 20151220: Invoking getShooter() with null but method isn't static try { if (getShooter == null) { getShooter = Projectile.class.getMethod("getShooter"); } - Object obj = getShooter.invoke(null); + Object obj = getShooter.invoke(projectile); player = (Player) obj; - } catch (Exception ignored) { + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + ConsoleLogger.logException("Error getting shooter", e); } } From bf91e7754e30d6b577c28c0be9f48e760b9411c5 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 May 2016 21:43:28 +0200 Subject: [PATCH 7/9] Remove unused imports --- .../fr/xephi/authme/listener/AuthMePlayerListener.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index fa3b5215..a974421a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -1,12 +1,8 @@ package fr.xephi.authme.listener; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.process.Management; @@ -75,11 +71,7 @@ public class AuthMePlayerListener implements Listener { @Inject private SpawnLoader spawnLoader; @Inject - private PluginHooks pluginHooks; - @Inject private OnJoinVerifier onJoinVerifier; - @Inject - private AuthMe plugin; @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { @@ -379,7 +371,7 @@ public class AuthMePlayerListener implements Listener { } } - // TODO: check this, why do we need to save the quit loc? + // TODO: check this, why do we need to update the quit loc? -sgdc3 @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlayerRespawn(PlayerRespawnEvent event) { if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { From 30aace06d4a32f717fb2330999af5e4b65c9112a Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 May 2016 21:47:54 +0200 Subject: [PATCH 8/9] Remove unknown config entry --- src/main/resources/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3cb5f23f..abf71daf 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -69,9 +69,6 @@ settings: allowChat: false # Can not authenticated players see the chat log? hideChat: false - # WARNING: use this only if you need it! - # Allow unlogged users to use all the commands if registration is not forced! - allowAllCommandsIfRegistrationIsOptional: false # Commands allowed when a player is not authenticated allowCommands: - /login From cc67624a468c442bb5ec59bac9e2ce72709cdf15 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 30 May 2016 17:09:10 +0200 Subject: [PATCH 9/9] Throwing snowball still possible when unlogged --- .../authme/listener/AuthMeEntityListener.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java index 1c6522c9..f2686f6a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeEntityListener.java @@ -25,14 +25,15 @@ import static fr.xephi.authme.listener.ListenerService.shouldCancelEvent; public class AuthMeEntityListener implements Listener { - private static Method getShooter; - private static boolean shooterIsProjectileSource; + private Method getShooter; + private boolean shooterIsProjectileSource; public AuthMeEntityListener() { try { - Method m = Projectile.class.getDeclaredMethod("getShooter"); - shooterIsProjectileSource = m.getReturnType() != LivingEntity.class; - } catch (Exception ignored) { + getShooter = Projectile.class.getDeclaredMethod("getShooter"); + shooterIsProjectileSource = getShooter.getReturnType() != LivingEntity.class; + } catch (NoSuchMethodException | SecurityException e) { + ConsoleLogger.logException("Cannot load getShooter() method on Projectile class", e); } } @@ -90,7 +91,7 @@ public class AuthMeEntityListener implements Listener { } } - // In old versions of the Bukkit API getShooter() returns a Player Object instead of a ProjectileSource + // TODO #733: Player can't throw snowball but the item is taken. @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { if (event.getEntity() == null) { @@ -99,6 +100,7 @@ public class AuthMeEntityListener implements Listener { Player player = null; Projectile projectile = event.getEntity(); + // In old versions of the Bukkit API getShooter() returns a Player object instead of a ProjectileSource if (shooterIsProjectileSource) { ProjectileSource shooter = projectile.getShooter(); if (shooter == null || !(shooter instanceof Player)) {