From d30580d5d45a33d17912b471c1956a050194d024 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 11 Aug 2019 23:47:50 +0200 Subject: [PATCH] Listener optimizations (#1884) * Drop CraftBukkit support, listeners cleanup * Codestyle * Codestyle * Remove useless player.saveData() calls * Micro optimization on the join process * Requested changes * Fix unit test * Test onPlayerHeldItem listener * Requested changes * Remove unused import --- src/main/java/fr/xephi/authme/AuthMe.java | 6 +- .../xephi/authme/listener/BlockListener.java | 1 - .../xephi/authme/listener/EntityListener.java | 42 +- .../xephi/authme/listener/JoiningPlayer.java | 66 -- .../xephi/authme/listener/OnJoinVerifier.java | 16 +- .../xephi/authme/listener/PlayerListener.java | 570 ++++++++---------- .../authme/listener/PlayerListener19.java | 1 - .../listener/PlayerListener19Spigot.java | 1 - .../xephi/authme/listener/ServerListener.java | 10 - .../fr/xephi/authme/message/MessageKey.java | 3 + .../authme/permission/PermissionsManager.java | 13 - .../authme/process/join/AsynchronousJoin.java | 3 +- .../process/login/ProcessSyncPlayerLogin.java | 1 - .../register/ProcessSyncEmailRegister.java | 1 - .../register/ProcessSyncPasswordRegister.java | 1 - .../unregister/AsynchronousUnregister.java | 1 - .../settings/properties/PluginSettings.java | 8 - src/main/resources/messages/messages_en.yml | 1 + .../authme/listener/EntityListenerTest.java | 3 - .../listener/ListenerConsistencyTest.java | 11 +- .../authme/listener/OnJoinVerifierTest.java | 48 +- .../authme/listener/PlayerListenerTest.java | 207 ++++++- .../authme/listener/ServerListenerTest.java | 13 - .../permission/PermissionsManagerTest.java | 28 - 24 files changed, 498 insertions(+), 557 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/listener/JoiningPlayer.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 42d6c9a4..c5c7d01c 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -125,8 +125,10 @@ public class AuthMe extends JavaPlugin { logger = ConsoleLoggerFactory.get(AuthMe.class); // Check server version - if (!isClassLoaded("org.bukkit.event.player.PlayerInteractAtEntityEvent")) { - logger.warning("You are running an unsupported server version! AuthMe requires MC 1.8.X or later!"); + if (!isClassLoaded("org.spigotmc.event.player.PlayerSpawnLocationEvent") + || !isClassLoaded("org.bukkit.event.player.PlayerInteractAtEntityEvent")) { + logger.warning("You are running an unsupported server version!" + + "AuthMe requires Spigot 1.8.X or later!"); stopOrUnload(); return; } diff --git a/src/main/java/fr/xephi/authme/listener/BlockListener.java b/src/main/java/fr/xephi/authme/listener/BlockListener.java index 64ef09ea..e40bf8d0 100644 --- a/src/main/java/fr/xephi/authme/listener/BlockListener.java +++ b/src/main/java/fr/xephi/authme/listener/BlockListener.java @@ -25,5 +25,4 @@ public class BlockListener implements Listener { event.setCancelled(true); } } - } diff --git a/src/main/java/fr/xephi/authme/listener/EntityListener.java b/src/main/java/fr/xephi/authme/listener/EntityListener.java index 609edaae..35e10920 100644 --- a/src/main/java/fr/xephi/authme/listener/EntityListener.java +++ b/src/main/java/fr/xephi/authme/listener/EntityListener.java @@ -1,8 +1,5 @@ package fr.xephi.authme.listener; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.output.ConsoleLoggerFactory; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; @@ -16,32 +13,19 @@ import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.projectiles.ProjectileSource; import javax.inject.Inject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; public class EntityListener implements Listener { - private final ConsoleLogger logger = ConsoleLoggerFactory.get(EntityListener.class); private final ListenerService listenerService; - private Method getShooter; - private boolean shooterIsLivingEntity; - @Inject EntityListener(ListenerService listenerService) { this.listenerService = listenerService; - - try { - getShooter = Projectile.class.getDeclaredMethod("getShooter"); - shooterIsLivingEntity = getShooter.getReturnType() == LivingEntity.class; - } catch (NoSuchMethodException | SecurityException e) { - logger.logException("Cannot load getShooter() method on Projectile class", e); - } } - // Note #360: npc status can be used to bypass security!!! @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onDamage(EntityDamageEvent event) { if (listenerService.shouldCancelEvent(event)) { @@ -81,6 +65,7 @@ public class EntityListener implements Listener { } } + //TODO sgdc3 20190808: We listen at the same event twice, does it make any sense? @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onEntityInteract(EntityInteractEvent event) { if (listenerService.shouldCancelEvent(event)) { @@ -97,26 +82,10 @@ public class EntityListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { - if (event.getEntity() == null) { - return; - } + final Projectile projectile = event.getEntity(); - Projectile projectile = event.getEntity(); - // In the Bukkit API prior to 1.7, getShooter() returns a LivingEntity instead of a ProjectileSource - Object shooterRaw = null; - if (shooterIsLivingEntity) { - try { - if (getShooter == null) { - getShooter = Projectile.class.getMethod("getShooter"); - } - shooterRaw = getShooter.invoke(projectile); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - logger.logException("Error getting shooter", e); - } - } else { - shooterRaw = projectile.getShooter(); - } - if (shooterRaw instanceof Player && listenerService.shouldCancelEvent((Player) shooterRaw)) { + ProjectileSource shooter = projectile.getShooter(); + if (shooter instanceof Player && listenerService.shouldCancelEvent((Player) shooter)) { event.setCancelled(true); } } @@ -127,5 +96,4 @@ public class EntityListener implements Listener { event.setCancelled(true); } } - } diff --git a/src/main/java/fr/xephi/authme/listener/JoiningPlayer.java b/src/main/java/fr/xephi/authme/listener/JoiningPlayer.java deleted file mode 100644 index e0bd1772..00000000 --- a/src/main/java/fr/xephi/authme/listener/JoiningPlayer.java +++ /dev/null @@ -1,66 +0,0 @@ -package fr.xephi.authme.listener; - -import fr.xephi.authme.permission.PermissionNode; -import fr.xephi.authme.permission.PermissionsManager; -import org.bukkit.entity.Player; - -import java.util.function.BiFunction; - -/** - * Represents a player joining the server, which depending on the available - * information may be his name or the actual Player object. - */ -public final class JoiningPlayer { - - private final String name; - private final BiFunction permissionLookupFunction; - - /** - * Hidden constructor. - * - * @param name the player's name - * @param permFunction the function to use for permission lookups - */ - private JoiningPlayer(String name, BiFunction permFunction) { - this.name = name; - this.permissionLookupFunction = permFunction; - } - - /** - * Creates a {@link JoiningPlayer} instance from the given name. - * - * @param name the player's name - * @return the created instance - */ - public static JoiningPlayer fromName(String name) { - return new JoiningPlayer(name, (manager, perm) -> manager.hasPermissionOffline(name, perm)); - } - - /** - * Creates a {@link JoiningPlayer} instance from the given Player object. - * - * @param player the player - * @return the created instance - */ - public static JoiningPlayer fromPlayerObject(Player player) { - return new JoiningPlayer(player.getName(), (manager, perm) -> manager.hasPermission(player, perm)); - } - - /** - * @return the player's name - */ - public String getName() { - return name; - } - - /** - * Returns the function to use for permission lookups. Takes two arguments: the PermissionsManager instance, - * and the permission node to look up. The result is a boolean indicating whether or not this joining player - * has permission. - * - * @return the permissions lookup function to use - */ - public BiFunction getPermissionLookupFunction() { - return permissionLookupFunction; - } -} diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index aea422de..ce61b8f2 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -31,7 +31,7 @@ import java.util.regex.Pattern; * Service for performing various verifications when a player joins. */ public class OnJoinVerifier implements Reloadable { - + private final ConsoleLogger logger = ConsoleLoggerFactory.get(OnJoinVerifier.class); @Inject @@ -67,16 +67,16 @@ public class OnJoinVerifier implements Reloadable { /** * Checks if Antibot is enabled. * - * @param joiningPlayer the joining player to check + * @param name the joining player name to check * @param isAuthAvailable whether or not the player is registered * @throws FailedVerificationException if the verification fails */ - public void checkAntibot(JoiningPlayer joiningPlayer, boolean isAuthAvailable) throws FailedVerificationException { - if (isAuthAvailable || permissionsManager.hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT)) { + public void checkAntibot(String name, boolean isAuthAvailable) throws FailedVerificationException { + if (isAuthAvailable || permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)) { return; } if (antiBotService.shouldKick()) { - antiBotService.addPlayerKick(joiningPlayer.getName()); + antiBotService.addPlayerKick(name); throw new FailedVerificationException(MessageKey.KICK_ANTIBOT); } } @@ -170,16 +170,16 @@ public class OnJoinVerifier implements Reloadable { /** * Checks that the player's country is admitted. * - * @param joiningPlayer the joining player to verify + * @param name the joining player name to verify * @param address the player address * @param isAuthAvailable whether or not the user is registered * @throws FailedVerificationException if the verification fails */ - public void checkPlayerCountry(JoiningPlayer joiningPlayer, String address, + public void checkPlayerCountry(String name, String address, boolean isAuthAvailable) throws FailedVerificationException { if ((!isAuthAvailable || settings.getProperty(ProtectionSettings.ENABLE_PROTECTION_REGISTERED)) && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION) - && !permissionsManager.hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_COUNTRY_CHECK) + && !permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_COUNTRY_CHECK) && !validationService.isCountryAdmitted(address)) { throw new FailedVerificationException(MessageKey.COUNTRY_BANNED_ERROR); } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index bd165b7a..34cf62a6 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -1,30 +1,23 @@ package fr.xephi.authme.listener; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.QuickCommandsProtectionManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.permission.handlers.PermissionLoadUserException; import fr.xephi.authme.process.Management; import fr.xephi.authme.service.AntiBotService; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.JoinMessageService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.service.ValidationService; -import fr.xephi.authme.service.bungeecord.BungeeSender; -import fr.xephi.authme.service.bungeecord.MessageType; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.util.PlayerUtils; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.entity.HumanEntity; @@ -59,7 +52,6 @@ import org.bukkit.event.player.PlayerShearEntityEvent; import org.bukkit.inventory.InventoryView; import javax.inject.Inject; -import java.util.HashSet; import java.util.Set; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; @@ -70,8 +62,6 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAU */ public class PlayerListener implements Listener { - private final ConsoleLogger logger = ConsoleLoggerFactory.get(PlayerListener.class); - @Inject private Settings settings; @Inject @@ -100,106 +90,104 @@ public class PlayerListener implements Listener { private PermissionsManager permissionsManager; @Inject private QuickCommandsProtectionManager quickCommandsProtectionManager; - @Inject - private BungeeSender bungeeSender; - private boolean isAsyncPlayerPreLoginEventCalled = false; - private Set unresolvedPlayerHostname = new HashSet<>(); - - @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)) { + // Lowest priority to apply fast protection checks + @EventHandler(priority = EventPriority.LOWEST) + public void onAsyncPlayerPreLoginEventLowest(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { return; } - if (settings.getProperty(RestrictionSettings.ALLOW_COMMANDS).contains(cmd)) { + final String name = event.getName(); + + // NOTE: getAddress() sometimes returning null, we don't want to handle this race condition + if (event.getAddress() == null) { + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, + messages.retrieveSingle(name, MessageKey.KICK_UNRESOLVED_HOSTNAME)); return; } + + if (validationService.isUnrestricted(name)) { + return; + } + + // Non-blocking checks + try { + onJoinVerifier.checkSingleSession(name); + onJoinVerifier.checkIsValidName(name); + } catch (FailedVerificationException e) { + event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs())); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } + } + + /* + * Login/join/leave events + */ + + // Note: at this stage (HIGHEST priority) the user's permission data should already have been loaded by + // the permission handler, we don't need to call permissionsManager.loadUserData() + + @EventHandler(priority = EventPriority.HIGHEST) + public void onAsyncPlayerPreLoginEventHighest(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + return; + } + final String name = event.getName(); + + if (validationService.isUnrestricted(name)) { + return; + } + + // Slow, blocking checks + try { + final PlayerAuth auth = dataSource.getAuth(name); + final boolean isAuthAvailable = auth != null; + onJoinVerifier.checkKickNonRegistered(isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); + onJoinVerifier.checkNameCasing(name, auth); + final String ip = event.getAddress().getHostAddress(); + onJoinVerifier.checkPlayerCountry(name, ip, isAuthAvailable); + } catch (FailedVerificationException e) { + event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs())); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } + } + + // Note: We can't teleport the player in the PlayerLoginEvent listener + // as the new player location will be reverted by the server. + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerLogin(PlayerLoginEvent event) { final Player player = event.getPlayer(); - if (!quickCommandsProtectionManager.isAllowed(player.getName())) { - event.setCancelled(true); - player.kickPlayer(messages.retrieveSingle(player, MessageKey.QUICK_COMMAND_PROTECTION_KICK)); + final String name = player.getName(); + + if (validationService.isUnrestricted(name)) { return; } - if (listenerService.shouldCancelEvent(player)) { - event.setCancelled(true); - messages.send(player, MessageKey.DENIED_COMMAND); - } + + onJoinVerifier.refusePlayerForFullServer(event); } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerChat(AsyncPlayerChatEvent event) { - if (settings.getProperty(RestrictionSettings.ALLOW_CHAT)) { - return; - } - + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - final boolean mayPlayerSendChat = !listenerService.shouldCancelEvent(player) - || permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_CHAT_BEFORE_LOGIN); - if (mayPlayerSendChat) { - removeUnauthorizedRecipients(event); - } else { - event.setCancelled(true); - messages.send(player, MessageKey.DENIED_CHAT); - } - } - private void removeUnauthorizedRecipients(AsyncPlayerChatEvent event) { - if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { - event.getRecipients().removeIf(listenerService::shouldCancelEvent); - if (event.getRecipients().isEmpty()) { - event.setCancelled(true); - } - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onPlayerMove(PlayerMoveEvent event) { - if (settings.getProperty(ALLOW_UNAUTHED_MOVEMENT) && settings.getProperty(ALLOWED_MOVEMENT_RADIUS) <= 0) { - return; + if (!PlayerListener19Spigot.isPlayerSpawnLocationEventCalled()) { + teleportationService.teleportOnJoin(player); } - /* - * Limit player X and Z movements to 1 block - * Deny player Y+ movements (allows falling) - */ - Location from = event.getFrom(); - Location to = event.getTo(); - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ() - && from.getY() - to.getY() >= 0) { - return; - } + quickCommandsProtectionManager.processJoin(player); - Player player = event.getPlayer(); - if (!listenerService.shouldCancelEvent(player)) { - return; - } + management.performJoin(player); - if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) { - // "cancel" the event - event.setTo(event.getFrom()); - return; - } - - if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { - return; - } - - Location spawn = spawnLoader.getSpawnLocation(player); - if (spawn != null && spawn.getWorld() != null) { - if (!player.getWorld().equals(spawn.getWorld())) { - player.teleport(spawn); - } else if (spawn.distance(player.getLocation()) > settings.getProperty(ALLOWED_MOVEMENT_RADIUS)) { - player.teleport(spawn); - } - } + teleportationService.teleportNewPlayerToFirstSpawn(player); } @EventHandler(priority = EventPriority.HIGHEST) public void onJoinMessage(PlayerJoinEvent event) { final Player player = event.getPlayer(); + // Note: join message can be null, despite api documentation says not if (settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)) { event.setJoinMessage(null); return; @@ -229,131 +217,11 @@ public class PlayerListener implements Listener { } } - @EventHandler(priority = EventPriority.NORMAL) - public void onPlayerJoin(PlayerJoinEvent event) { - final Player player = event.getPlayer(); - - if (unresolvedPlayerHostname.remove(player.getName())) { - try { - runOnJoinChecks(JoiningPlayer.fromPlayerObject(player), PlayerUtils.getPlayerIp(player)); - } catch (FailedVerificationException e) { - player.kickPlayer(messages.retrieveSingle(player, e.getReason(), e.getArgs())); - return; - } - } - - if (!PlayerListener19Spigot.isPlayerSpawnLocationEventCalled()) { - teleportationService.teleportOnJoin(player); - } - - quickCommandsProtectionManager.processJoin(player); - - management.performJoin(player); - - teleportationService.teleportNewPlayerToFirstSpawn(player); - } - - private void runOnJoinChecks(JoiningPlayer joiningPlayer, String ip) throws FailedVerificationException { - // Fast stuff - final String name = joiningPlayer.getName(); - onJoinVerifier.checkSingleSession(name); - onJoinVerifier.checkIsValidName(name); - - // Get the auth later as this may cause the single session check to fail - // Slow stuff - final PlayerAuth auth = dataSource.getAuth(name); - final boolean isAuthAvailable = auth != null; - onJoinVerifier.checkKickNonRegistered(isAuthAvailable); - onJoinVerifier.checkAntibot(joiningPlayer, isAuthAvailable); - onJoinVerifier.checkNameCasing(name, auth); - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, isAuthAvailable); - } - - // Note #831: AsyncPlayerPreLoginEvent is not fired by all servers in offline mode - // e.g. CraftBukkit does not fire it. So we need to run crucial things with PlayerLoginEvent. - // Single session feature can be implemented for Spigot and CraftBukkit by canceling a kick - // event caused by "logged in from another location". The nicer way, but only for Spigot, would be - // to check in the AsyncPlayerPreLoginEvent. To support all servers, we use the less nice way. - - @EventHandler(priority = EventPriority.HIGHEST) - public void onAsyncPlayerPreLoginEvent(AsyncPlayerPreLoginEvent event) { - isAsyncPlayerPreLoginEventCalled = true; - if (!settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT)) { - return; - } - - final String name = event.getName(); - - if (validationService.isUnrestricted(name)) { - return; - } - - // Keep pre-UUID compatibility - try { - permissionsManager.loadUserData(event.getUniqueId()); - } catch (PermissionLoadUserException e) { - logger.logException("Unable to load the permission data of user " + name, e); - } - - // getAddress() sometimes returning null if not yet resolved - // skip it and let PlayerLoginEvent to handle it - if (event.getAddress() == null) { - unresolvedPlayerHostname.add(event.getName()); - return; - } else { - unresolvedPlayerHostname.remove(event.getName()); - } - - try { - runOnJoinChecks(JoiningPlayer.fromName(name), event.getAddress().getHostAddress()); - } catch (FailedVerificationException e) { - event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs())); - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - } - } - - //Note: We can't teleport the player in the PlayerLoginEvent listener - //as the new player location will be reverted by the server. - - @EventHandler(priority = EventPriority.LOW) - public void onPlayerLogin(PlayerLoginEvent event) { - final Player player = event.getPlayer(); - final String name = player.getName(); - - if (validationService.isUnrestricted(name)) { - return; - } - - if (onJoinVerifier.refusePlayerForFullServer(event)) { - return; - } - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { - return; - } - - - if (event.getAddress() == null) { // Address still null - unresolvedPlayerHostname.add(name); - return; - } else { - unresolvedPlayerHostname.remove(name); - } - - if (!isAsyncPlayerPreLoginEventCalled || !settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT)) { - try { - // Player.getAddress() can be null at this event, use event.getAddress() - runOnJoinChecks(JoiningPlayer.fromPlayerObject(player), event.getAddress().getHostAddress()); - } catch (FailedVerificationException e) { - event.setKickMessage(messages.retrieveSingle(player, e.getReason(), e.getArgs())); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - } - } - } - @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); + // Note: quit message can be null, despite api documentation says not if (settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)) { event.setQuitMessage(null); } else if (settings.getProperty(RegistrationSettings.REMOVE_UNLOGGED_LEAVE_MESSAGE)) { @@ -385,6 +253,195 @@ public class PlayerListener implements Listener { } } + /* + * Chat/command events + */ + + private void removeUnauthorizedRecipients(AsyncPlayerChatEvent event) { + if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { + event.getRecipients().removeIf(listenerService::shouldCancelEvent); + if (event.getRecipients().isEmpty()) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (settings.getProperty(RestrictionSettings.ALLOW_CHAT)) { + return; + } + + final Player player = event.getPlayer(); + final boolean mayPlayerSendChat = !listenerService.shouldCancelEvent(player) + || permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_CHAT_BEFORE_LOGIN); + if (mayPlayerSendChat) { + removeUnauthorizedRecipients(event); + } else { + event.setCancelled(true); + messages.send(player, MessageKey.DENIED_CHAT); + } + } + + @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(RestrictionSettings.ALLOW_COMMANDS).contains(cmd)) { + return; + } + final Player player = event.getPlayer(); + if (!quickCommandsProtectionManager.isAllowed(player.getName())) { + event.setCancelled(true); + player.kickPlayer(messages.retrieveSingle(player, MessageKey.QUICK_COMMAND_PROTECTION_KICK)); + return; + } + if (listenerService.shouldCancelEvent(player)) { + event.setCancelled(true); + messages.send(player, MessageKey.DENIED_COMMAND); + } + } + + /* + * Movement events + */ + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onPlayerMove(PlayerMoveEvent event) { + if (settings.getProperty(ALLOW_UNAUTHED_MOVEMENT) && settings.getProperty(ALLOWED_MOVEMENT_RADIUS) <= 0) { + return; + } + + Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null) { + return; + } + + /* + * Limit player X and Z movements to 1 block + * Deny player Y+ movements (allows falling) + */ + + if (from.getBlockX() == to.getBlockX() + && from.getBlockZ() == to.getBlockZ() + && from.getY() - to.getY() >= 0) { + return; + } + + Player player = event.getPlayer(); + if (!listenerService.shouldCancelEvent(player)) { + return; + } + + if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) { + // "cancel" the event + event.setTo(event.getFrom()); + return; + } + + if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { + return; + } + + Location spawn = spawnLoader.getSpawnLocation(player); + if (spawn != null && spawn.getWorld() != null) { + if (!player.getWorld().equals(spawn.getWorld())) { + player.teleport(spawn); + } else if (spawn.distance(player.getLocation()) > settings.getProperty(ALLOWED_MOVEMENT_RADIUS)) { + player.teleport(spawn); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onPlayerRespawn(PlayerRespawnEvent event) { + if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { + return; + } + if (!listenerService.shouldCancelEvent(event)) { + return; + } + Location spawn = spawnLoader.getSpawnLocation(event.getPlayer()); + if (spawn != null && spawn.getWorld() != null) { + event.setRespawnLocation(spawn); + } + } + + /* + * Entity/block interaction events + */ + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerHitPlayerEvent(EntityDamageByEntityEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerShear(PlayerShearEntityEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerFish(PlayerFishEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerBedEnter(PlayerBedEnterEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerEditBook(PlayerEditBookEvent event) { + if (listenerService.shouldCancelEvent(event)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onSignChange(SignChangeEvent event) { + Player player = event.getPlayer(); + if (listenerService.shouldCancelEvent(player)) { + event.setCancelled(true); + } + } + + /* + * Inventory interactions + */ + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerPickupItem(PlayerPickupItemEvent event) { if (listenerService.shouldCancelEvent(event)) { @@ -393,14 +450,14 @@ public class PlayerListener implements Listener { } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerHeldItem(PlayerItemHeldEvent event) { + public void onPlayerDropItem(PlayerDropItemEvent event) { if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerInteract(PlayerInteractEvent event) { + public void onPlayerHeldItem(PlayerItemHeldEvent event) { if (listenerService.shouldCancelEvent(event)) { event.setCancelled(true); } @@ -444,95 +501,4 @@ public class PlayerListener implements Listener { event.setCancelled(true); } } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerHitPlayerEvent(EntityDamageByEntityEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerDropItem(PlayerDropItemEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerBedEnter(PlayerBedEnterEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onSignChange(SignChangeEvent event) { - Player player = event.getPlayer(); - if (listenerService.shouldCancelEvent(player)) { - event.setCancelled(true); - } - } - - //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)) { - return; - } - if (!listenerService.shouldCancelEvent(event)) { - return; - } - Player player = event.getPlayer(); - String name = player.getName().toLowerCase(); - Location spawn = spawnLoader.getSpawnLocation(player); - if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && dataSource.isAuthAvailable(name)) { - PlayerAuth auth = PlayerAuth.builder() - .name(name) - .realName(player.getName()) - .location(spawn) - .build(); - dataSource.updateQuitLoc(auth); - bungeeSender.sendAuthMeBungeecordMessage(MessageType.REFRESH_QUITLOC, name); - } - if (spawn != null && spawn.getWorld() != null) { - event.setRespawnLocation(spawn); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerShear(PlayerShearEntityEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerFish(PlayerFishEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerEditBook(PlayerEditBookEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { - if (listenerService.shouldCancelEvent(event)) { - event.setCancelled(true); - } - } - } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener19.java b/src/main/java/fr/xephi/authme/listener/PlayerListener19.java index b6db1802..aeb116dc 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener19.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener19.java @@ -21,5 +21,4 @@ public class PlayerListener19 implements Listener { event.setCancelled(true); } } - } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener19Spigot.java b/src/main/java/fr/xephi/authme/listener/PlayerListener19Spigot.java index 1684887e..a068f27d 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener19Spigot.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener19Spigot.java @@ -33,5 +33,4 @@ public class PlayerListener19Spigot implements Listener { event.setSpawnLocation(customSpawnLocation); } } - } diff --git a/src/main/java/fr/xephi/authme/listener/ServerListener.java b/src/main/java/fr/xephi/authme/listener/ServerListener.java index 60b04dbb..e7e3c4a9 100644 --- a/src/main/java/fr/xephi/authme/listener/ServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/ServerListener.java @@ -32,11 +32,6 @@ public class ServerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPluginDisable(PluginDisableEvent event) { - // Make sure the plugin instance isn't null - if (event.getPlugin() == null) { - return; - } - final String pluginName = event.getPlugin().getName(); // Call the onPluginDisable method in the permissions manager @@ -63,11 +58,6 @@ public class ServerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPluginEnable(PluginEnableEvent event) { - // Make sure the plugin instance isn't null - if (event.getPlugin() == null) { - return; - } - final String pluginName = event.getPlugin().getName(); // Call the onPluginEnable method in the permissions manager diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 6171f80a..c7ed8dc3 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -146,6 +146,9 @@ public enum MessageKey { /** The server is full, try again later! */ KICK_FULL_SERVER("on_join_validation.kick_full_server"), + /** An error occurred: unresolved player hostname! **/ + KICK_UNRESOLVED_HOSTNAME("error.kick_unresolved_hostname"), + /** Usage: /email add <email> <confirmEmail> */ USAGE_ADD_EMAIL("email.usage_email_add"), diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index e016b2b0..a05582f1 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -4,7 +4,6 @@ import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.output.ConsoleLoggerFactory; -import fr.xephi.authme.listener.JoiningPlayer; import fr.xephi.authme.permission.handlers.LuckPermsHandler; import fr.xephi.authme.permission.handlers.PermissionHandler; import fr.xephi.authme.permission.handlers.PermissionHandlerException; @@ -227,18 +226,6 @@ public class PermissionsManager implements Reloadable { return player.hasPermission(permissionNode.getNode()); } - /** - * Check if the given player has permission for the given permission node. - * - * @param joiningPlayer The player to check - * @param permissionNode The permission node to verify - * - * @return true if the player has permission, false otherwise - */ - public boolean hasPermission(JoiningPlayer joiningPlayer, PermissionNode permissionNode) { - return joiningPlayer.getPermissionLookupFunction().apply(this, permissionNode); - } - /** * Check if a player has permission for the given permission node. This is for offline player checks. * If no permissions system is used, then the player will not have permission. diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index 7544e4c2..3db02311 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -89,8 +89,9 @@ public class AsynchronousJoin implements AsynchronousProcess { } if (service.getProperty(RestrictionSettings.FORCE_SURVIVAL_MODE) + && player.getGameMode() != GameMode.SURVIVAL && !service.hasPermission(player, PlayerStatePermission.BYPASS_FORCE_SURVIVAL)) { - bukkitService.runTask(() -> player.setGameMode(GameMode.SURVIVAL)); + bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> player.setGameMode(GameMode.SURVIVAL)); } if (service.getProperty(HooksSettings.DISABLE_SOCIAL_SPY)) { diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java index 29bbe720..eb2aad81 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -100,7 +100,6 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { // The Login event now fires (as intended) after everything is processed bukkitService.callEvent(new LoginEvent(player)); - player.saveData(); // Login is done, display welcome message welcomeMessageConfiguration.sendWelcomeMessage(player); diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java index 486910b2..a3e91889 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -41,7 +41,6 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED); limboService.replaceTasksAfterRegistration(player); - player.saveData(); bukkitService.callEvent(new RegisterEvent(player)); logger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player)); } diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java index 5cf971ed..aefe3900 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -67,7 +67,6 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { service.send(player, MessageKey.ADD_EMAIL_MESSAGE); } - player.saveData(); bukkitService.callEvent(new RegisterEvent(player)); logger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player)); diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 808b1cdb..22d7cf9b 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -128,7 +128,6 @@ public class AsynchronousUnregister implements AsynchronousProcess { if (service.getProperty(RegistrationSettings.FORCE)) { teleportationService.teleportOnJoin(player); - player.saveData(); bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { limboService.createLimboPlayer(player, false); diff --git a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java index 48e48b19..99e31757 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java @@ -82,14 +82,6 @@ public final class PluginSettings implements SettingsHolder { public static final Property USE_ASYNC_TASKS = newProperty("settings.useAsyncTasks", true); - @Comment({ - "By default we handle the AsyncPlayerPreLoginEvent which makes the plugin faster", - "but it is incompatible with any permission plugin not included in our compatibility list.", - "If you have issues with permission checks on player join please disable this option." - }) - public static final Property USE_ASYNC_PRE_LOGIN_EVENT = - newProperty("settings.useAsyncPreLoginEvent", true); - @Comment("The name of the server, used in some placeholders.") public static final Property SERVER_NAME = newProperty("settings.serverName", "Your Minecraft Server"); diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index 917d57cd..9721716f 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -41,6 +41,7 @@ error: unexpected_error: '&4An unexpected error occurred, please contact an administrator!' kick_for_vip: '&3A VIP player has joined the server when it was full!' logged_in: '&cYou''re already logged in!' + kick_unresolved_hostname: '&cAn error occurred: unresolved player hostname!' # AntiBot antibot: diff --git a/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java b/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java index 73a29dd7..067ff889 100644 --- a/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.listener; -import fr.xephi.authme.ReflectionTestUtils; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; @@ -200,8 +199,6 @@ public class EntityListenerTest { @Test public void shouldHandleOldShooterMethod() { // given - ReflectionTestUtils.setField(listener, "shooterIsLivingEntity", true); - ReflectionTestUtils.setField(listener, "getShooter", null); Projectile projectile = mock(Projectile.class); Player shooter = mock(Player.class); given(projectile.getShooter()).willReturn(shooter); diff --git a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java index c8d1d804..7f320516 100644 --- a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java @@ -1,8 +1,8 @@ package fr.xephi.authme.listener; -import com.google.common.collect.Sets; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.TestHelper; +import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -27,13 +27,6 @@ public final class ListenerConsistencyTest { private static List> classes; - private static final Set CANCELED_EXCEPTIONS = Sets.newHashSet( - "PlayerListener#onPlayerJoin", "PlayerListener#onPlayerLogin", - "PlayerListener#onPlayerQuit", "ServerListener#onPluginDisable", - "ServerListener#onServerPing", "ServerListener#onPluginEnable", - "PlayerListener#onJoinMessage", "PlayerListener#onAsyncPlayerPreLoginEvent", - "PlayerListener19Spigot#onPlayerSpawn"); - @BeforeClass public static void collectListenerClasses() { ClassCollector collector = new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "listener"); @@ -84,7 +77,7 @@ public final class ListenerConsistencyTest { Method[] methods = listenerClass.getDeclaredMethods(); for (Method method : methods) { if (isTestableMethod(method) && method.isAnnotationPresent(EventHandler.class)) { - if (CANCELED_EXCEPTIONS.contains(clazz + "#" + method.getName())) { + if (!method.getParameterTypes()[0].isAssignableFrom(Cancellable.class)) { continue; } diff --git a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java index d8fd9d6b..ddcd5bc7 100644 --- a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java +++ b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java @@ -374,27 +374,27 @@ public class OnJoinVerifierTest { @Test public void shouldAllowUser() throws FailedVerificationException { // given - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("Bobby"); + String name = "Bobby"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); given(antiBotService.shouldKick()).willReturn(false); // when - onJoinVerifier.checkAntibot(joiningPlayer, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then - verify(permissionsManager).hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verify(antiBotService).shouldKick(); } @Test public void shouldAllowUserWithAuth() throws FailedVerificationException { // given - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("Lacey"); + String name = "Lacey"; boolean isAuthAvailable = true; // when - onJoinVerifier.checkAntibot(joiningPlayer, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then verifyZeroInteractions(permissionsManager, antiBotService); @@ -403,32 +403,32 @@ public class OnJoinVerifierTest { @Test public void shouldAllowUserWithBypassPermission() throws FailedVerificationException { // given - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("Steward"); + String name = "Steward"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(true); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(true); // when - onJoinVerifier.checkAntibot(joiningPlayer, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then - verify(permissionsManager).hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verifyZeroInteractions(antiBotService); } @Test - public void shouldKickUserForFailedAntibotCheck() throws FailedVerificationException { + public void shouldKickUserForFailedAntibotCheck() { // given - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("D3"); + String name = "D3"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); given(antiBotService.shouldKick()).willReturn(true); // when / then try { - onJoinVerifier.checkAntibot(joiningPlayer, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); fail("Expected exception to be thrown"); } catch (FailedVerificationException e) { - verify(permissionsManager).hasPermission(joiningPlayer, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verify(antiBotService).shouldKick(); } @@ -439,18 +439,18 @@ public class OnJoinVerifierTest { */ @Test public void shouldNotCheckCountry() throws FailedVerificationException { - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("david"); + String name = "david"; String ip = "127.0.0.1"; // protection setting disabled given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(false); given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION_REGISTERED)).willReturn(true); - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, false); + onJoinVerifier.checkPlayerCountry(name, ip, false); verifyZeroInteractions(validationService); // protection for registered players disabled given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION_REGISTERED)).willReturn(false); - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, true); + onJoinVerifier.checkPlayerCountry(name, ip, true); verifyZeroInteractions(validationService); } @@ -458,12 +458,12 @@ public class OnJoinVerifierTest { public void shouldCheckAndAcceptUnregisteredPlayerCountry() throws FailedVerificationException { // given String ip = "192.168.0.1"; - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("lucas"); + String name = "lucas"; given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(true); given(validationService.isCountryAdmitted(ip)).willReturn(true); // when - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, false); + onJoinVerifier.checkPlayerCountry(name, ip, false); // then verify(validationService).isCountryAdmitted(ip); @@ -473,13 +473,13 @@ public class OnJoinVerifierTest { public void shouldCheckAndAcceptRegisteredPlayerCountry() throws FailedVerificationException { // given String ip = "192.168.10.24"; - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("gabriel"); + String name = "gabriel"; given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(true); given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION_REGISTERED)).willReturn(true); given(validationService.isCountryAdmitted(ip)).willReturn(true); // when - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, true); + onJoinVerifier.checkPlayerCountry(name, ip, true); // then verify(validationService).isCountryAdmitted(ip); @@ -489,7 +489,7 @@ public class OnJoinVerifierTest { public void shouldThrowForBannedCountry() throws FailedVerificationException { // given String ip = "192.168.40.0"; - JoiningPlayer joiningPlayer = JoiningPlayer.fromName("bob"); + String name = "bob"; given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(true); given(validationService.isCountryAdmitted(ip)).willReturn(false); @@ -497,7 +497,7 @@ public class OnJoinVerifierTest { expectValidationExceptionWith(MessageKey.COUNTRY_BANNED_ERROR); // when - onJoinVerifier.checkPlayerCountry(joiningPlayer, ip, false); + onJoinVerifier.checkPlayerCountry(name, ip, false); } private void expectValidationExceptionWith(MessageKey messageKey, String... args) { diff --git a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java index 0625f1ff..acb12cce 100644 --- a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.listener; -import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.QuickCommandsProtectionManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; @@ -29,6 +28,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; @@ -38,12 +38,14 @@ import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; 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 org.bukkit.inventory.InventoryView; import org.junit.Test; @@ -58,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.UUID; import static com.google.common.collect.Sets.newHashSet; import static fr.xephi.authme.listener.EventCancelVerifier.withServiceMock; @@ -71,7 +74,6 @@ import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doThrow; @@ -188,7 +190,8 @@ public class PlayerListenerTest { .check(listener::onPlayerConsumeItem, PlayerItemConsumeEvent.class) .check(listener::onPlayerInteract, PlayerInteractEvent.class) .check(listener::onPlayerPickupItem, PlayerPickupItemEvent.class) - .check(listener::onPlayerInteractEntity, PlayerInteractEntityEvent.class); + .check(listener::onPlayerInteractEntity, PlayerInteractEntityEvent.class) + .check(listener::onPlayerHeldItem, PlayerItemHeldEvent.class); } @Test @@ -536,6 +539,98 @@ public class PlayerListenerTest { verifyNoModifyingCalls(event); } + @Test + public void shouldIgnorePlayerRespawnWithNoTeleport() { + // given + Player player = mock(Player.class); + Location respawnLocation = mock(Location.class); + PlayerRespawnEvent event = spy(new PlayerRespawnEvent(player, respawnLocation, false)); + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(true); + + // when + listener.onPlayerRespawn(event); + + // then + verifyZeroInteractions(listenerService); + verify(event, never()).setRespawnLocation(any()); + } + + @Test + public void shouldIgnorePlayerRespawn() { + // given + Player player = mock(Player.class); + Location respawnLocation = mock(Location.class); + PlayerRespawnEvent event = spy(new PlayerRespawnEvent(player, respawnLocation, false)); + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(false); + given(listenerService.shouldCancelEvent(event)).willReturn(false); + + // when + listener.onPlayerRespawn(event); + + // then + verifyZeroInteractions(spawnLoader); + verify(event, never()).setRespawnLocation(any()); + } + + @Test + public void shouldHandlePlayerRespawn() { + // given + Player player = mock(Player.class); + Location originalLocation = mock(Location.class); + Location newLocation = mock(Location.class); + World world = mock(World.class); + given(newLocation.getWorld()).willReturn(world); + PlayerRespawnEvent event = spy(new PlayerRespawnEvent(player, originalLocation, false)); + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(false); + given(listenerService.shouldCancelEvent(event)).willReturn(true); + given(spawnLoader.getSpawnLocation(player)).willReturn(newLocation); + + // when + listener.onPlayerRespawn(event); + + // then + verify(spawnLoader).getSpawnLocation(player); + verify(event).setRespawnLocation(newLocation); + } + + @Test + public void shouldIgnorePlayerRespawnUnloadedWorld() { + // given + Player player = mock(Player.class); + Location originalLocation = mock(Location.class); + Location newLocation = mock(Location.class); + given(newLocation.getWorld()).willReturn(null); + PlayerRespawnEvent event = spy(new PlayerRespawnEvent(player, originalLocation, false)); + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(false); + given(listenerService.shouldCancelEvent(event)).willReturn(true); + given(spawnLoader.getSpawnLocation(player)).willReturn(newLocation); + + // when + listener.onPlayerRespawn(event); + + // then + verify(spawnLoader).getSpawnLocation(player); + verify(event, never()).setRespawnLocation(any()); + } + + @Test + public void shouldHandlePlayerRespawnNoChanges() { + // given + Player player = mock(Player.class); + Location originalLocation = mock(Location.class); + PlayerRespawnEvent event = spy(new PlayerRespawnEvent(player, originalLocation, false)); + given(settings.getProperty(RestrictionSettings.NO_TELEPORT)).willReturn(false); + given(listenerService.shouldCancelEvent(event)).willReturn(true); + given(spawnLoader.getSpawnLocation(player)).willReturn(null); + + // when + listener.onPlayerRespawn(event); + + // then + verify(spawnLoader).getSpawnLocation(player); + verify(event, never()).setRespawnLocation(any()); + } + @Test public void shouldHandlePlayerJoining() { // given @@ -606,60 +701,113 @@ public class PlayerListenerTest { } @Test - public void shouldPerformAllJoinVerificationsSuccessfully() throws FailedVerificationException { + public void shouldPerformAllJoinVerificationsSuccessfullyPreLoginLowest() throws FailedVerificationException { + // given + String name = "someone"; + UUID uniqueId = UUID.fromString("753493c9-33ba-4a4a-bf61-1bce9d3c9a71"); + String ip = "12.34.56.78"; + + AsyncPlayerPreLoginEvent preLoginEvent = spy(new AsyncPlayerPreLoginEvent(name, mockAddrWithIp(ip), uniqueId)); + given(validationService.isUnrestricted(name)).willReturn(false); + + // when + listener.onAsyncPlayerPreLoginEventLowest(preLoginEvent); + + // then + verify(validationService).isUnrestricted(name); + verify(onJoinVerifier).checkSingleSession(name); + verify(onJoinVerifier).checkIsValidName(name); + verifyZeroInteractions(dataSource); + verifyNoModifyingCalls(preLoginEvent); + } + + @Test + public void shouldKickPreLoginLowestUnresolvedHostname() throws FailedVerificationException { + // given + String name = "someone"; + UUID uniqueId = UUID.fromString("753493c9-33ba-4a4a-bf61-1bce9d3c9a71"); + + @SuppressWarnings("ConstantConditions") + AsyncPlayerPreLoginEvent preLoginEvent = spy(new AsyncPlayerPreLoginEvent(name, null, uniqueId)); + given(messages.retrieveSingle(name, MessageKey.KICK_UNRESOLVED_HOSTNAME)).willReturn("Unresolved hostname"); + + // when + listener.onAsyncPlayerPreLoginEventLowest(preLoginEvent); + + // then + verify(preLoginEvent).disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Unresolved hostname"); + verifyNoMoreInteractions(onJoinVerifier); + } + + @Test + public void shouldPerformAllJoinVerificationsSuccessfullyPreLoginHighest() throws FailedVerificationException { + // given + String name = "someone"; + UUID uniqueId = UUID.fromString("753493c9-33ba-4a4a-bf61-1bce9d3c9a71"); + String ip = "12.34.56.78"; + + AsyncPlayerPreLoginEvent preLoginEvent = spy(new AsyncPlayerPreLoginEvent(name, mockAddrWithIp(ip), uniqueId)); + given(validationService.isUnrestricted(name)).willReturn(false); + PlayerAuth auth = PlayerAuth.builder().name(name).build(); + given(dataSource.getAuth(name)).willReturn(auth); + + // when + listener.onAsyncPlayerPreLoginEventHighest(preLoginEvent); + + // then + verify(validationService).isUnrestricted(name); + verify(onJoinVerifier).checkKickNonRegistered(true); + verify(onJoinVerifier).checkAntibot(name, true); + verify(onJoinVerifier).checkNameCasing(name, auth); + verify(onJoinVerifier).checkPlayerCountry(name, ip, true); + verifyNoModifyingCalls(preLoginEvent); + } + + @Test + public void shouldPerformAllJoinVerificationsSuccessfullyLogin() { // given String name = "someone"; Player player = mockPlayerWithName(name); String ip = "12.34.56.78"; - PlayerLoginEvent event = spy(new PlayerLoginEvent(player, "", mockAddrWithIp(ip))); + PlayerLoginEvent loginEvent = spy(new PlayerLoginEvent(player, "", mockAddrWithIp(ip))); given(validationService.isUnrestricted(name)).willReturn(false); - given(onJoinVerifier.refusePlayerForFullServer(event)).willReturn(false); - PlayerAuth auth = PlayerAuth.builder().name(name).build(); - given(dataSource.getAuth(name)).willReturn(auth); + given(onJoinVerifier.refusePlayerForFullServer(loginEvent)).willReturn(false); // when - listener.onPlayerLogin(event); + listener.onPlayerLogin(loginEvent); // then verify(validationService).isUnrestricted(name); - verify(onJoinVerifier).refusePlayerForFullServer(event); - verify(onJoinVerifier).checkSingleSession(name); - verify(onJoinVerifier).checkIsValidName(name); - verify(onJoinVerifier).checkAntibot(any(JoiningPlayer.class), eq(true)); - verify(onJoinVerifier).checkKickNonRegistered(true); - verify(onJoinVerifier).checkNameCasing(name, auth); - verify(onJoinVerifier).checkPlayerCountry(any(JoiningPlayer.class), eq(ip), eq(true)); - verifyNoModifyingCalls(event); + verify(onJoinVerifier).refusePlayerForFullServer(loginEvent); + verifyZeroInteractions(dataSource); + verifyNoModifyingCalls(loginEvent); } @Test public void shouldAbortPlayerJoinForInvalidName() throws FailedVerificationException { // given String name = "inval!dName"; - Player player = mockPlayerWithName(name); - TestHelper.mockPlayerIp(player, "33.32.33.33"); - PlayerLoginEvent event = spy(new PlayerLoginEvent(player, "", player.getAddress().getAddress())); + UUID uniqueId = UUID.fromString("753493c9-33ba-4a4a-bf61-1bce9d3c9a71"); + InetAddress ip = mockAddrWithIp("33.32.33.33"); + AsyncPlayerPreLoginEvent event = spy(new AsyncPlayerPreLoginEvent(name, ip, uniqueId)); given(validationService.isUnrestricted(name)).willReturn(false); - given(onJoinVerifier.refusePlayerForFullServer(event)).willReturn(false); FailedVerificationException exception = new FailedVerificationException( MessageKey.INVALID_NAME_CHARACTERS, "[a-z]"); doThrow(exception).when(onJoinVerifier).checkIsValidName(name); String message = "Invalid characters!"; - given(messages.retrieveSingle(player, exception.getReason(), exception.getArgs())).willReturn(message); + given(messages.retrieveSingle(name, exception.getReason(), exception.getArgs())).willReturn(message); // when - listener.onPlayerLogin(event); + listener.onAsyncPlayerPreLoginEventLowest(event); // then verify(validationService).isUnrestricted(name); - verify(onJoinVerifier).refusePlayerForFullServer(event); - verify(onJoinVerifier).checkSingleSession(name); verify(onJoinVerifier).checkIsValidName(name); // Check that we don't talk with the data source before performing checks that don't require it verifyZeroInteractions(dataSource); verify(event).setKickMessage(message); - verify(event).setResult(PlayerLoginEvent.Result.KICK_OTHER); + verify(event).setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); } @Test @@ -950,6 +1098,13 @@ public class PlayerListenerTest { verifyNoMoreInteractions(event); } + private static void verifyNoModifyingCalls(AsyncPlayerPreLoginEvent event) { + verify(event, atLeast(0)).getLoginResult(); + verify(event, atLeast(0)).getAddress(); + verify(event, atLeast(0)).getName(); + verifyNoMoreInteractions(event); + } + private static InetAddress mockAddrWithIp(String ip) { InetAddress addr = mock(InetAddress.class); given(addr.getHostAddress()).willReturn(ip); diff --git a/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java b/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java index b015c2eb..7b82bc4c 100644 --- a/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java @@ -81,19 +81,6 @@ public class ServerListenerTest { checkDisableHandling("UnknownPlugin", () -> verifyZeroInteractions(pluginHookService, spawnLoader)); } - @Test - public void shouldHandlePluginWithNullName() { - PluginEnableEvent enableEvent = mock(PluginEnableEvent.class); - given(enableEvent.getPlugin()).willReturn(null); - serverListener.onPluginEnable(enableEvent); - verifyNoMoreInteractionsAndReset(); - - PluginDisableEvent disableEvent = mock(PluginDisableEvent.class); - given(disableEvent.getPlugin()).willReturn(null); - serverListener.onPluginDisable(disableEvent); - verifyNoMoreInteractionsAndReset(); - } - private void checkEnableHandling(String pluginName, Runnable verifier) { PluginEnableEvent event = mockEventWithPluginName(PluginEnableEvent.class, pluginName); serverListener.onPluginEnable(event); diff --git a/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java b/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java index 8468d433..6c7efb45 100644 --- a/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java +++ b/src/test/java/fr/xephi/authme/permission/PermissionsManagerTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.permission; -import fr.xephi.authme.listener.JoiningPlayer; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -13,13 +12,8 @@ import org.mockito.junit.MockitoJUnitRunner; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; /** * Test for {@link PermissionsManager}. @@ -154,26 +148,4 @@ public class PermissionsManagerTest { // then assertThat(result, equalTo(true)); } - - @Test - public void shouldHandleJoiningPlayerPermissionChecksWithProperMethod() { - // given - Player player = mock(Player.class); - JoiningPlayer fromPlayer = JoiningPlayer.fromPlayerObject(player); - JoiningPlayer fromName = JoiningPlayer.fromName("Chris"); - - PermissionsManager permManagerSpy = spy(permissionsManager); - given(permManagerSpy.hasPermission(any(Player.class), eq(PlayerPermission.LOGIN))).willReturn(true); - given(permManagerSpy.hasPermissionOffline(anyString(), eq(PlayerPermission.UNREGISTER))).willReturn(true); - - // when - boolean resultFromPlayer = permManagerSpy.hasPermission(fromPlayer, PlayerPermission.LOGIN); - boolean resultFromName = permManagerSpy.hasPermission(fromName, PlayerPermission.UNREGISTER); - - // then - assertThat(resultFromPlayer, equalTo(true)); - assertThat(resultFromName, equalTo(true)); - verify(permManagerSpy).hasPermission(player, PlayerPermission.LOGIN); - verify(permManagerSpy).hasPermissionOffline(fromName.getName(), PlayerPermission.UNREGISTER); - } }