diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index 71fa2fc1f..bc31f4852 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -16,6 +16,7 @@ import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -42,10 +43,6 @@ import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerShearEntityEvent; import javax.inject.Inject; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT; @@ -107,15 +104,8 @@ public class PlayerListener implements Listener { event.setCancelled(true); m.send(player, MessageKey.DENIED_CHAT); } else if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) { - Set recipients = event.getRecipients(); - Iterator iter = recipients.iterator(); - while (iter.hasNext()) { - Player p = iter.next(); - if (listenerService.shouldCancelEvent(p)) { - iter.remove(); - } - } - if (recipients.isEmpty()) { + event.getRecipients().removeIf(listenerService::shouldCancelEvent); + if (event.getRecipients().isEmpty()) { event.setCancelled(true); } } @@ -300,18 +290,17 @@ public class PlayerListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInventoryOpen(InventoryOpenEvent event) { - final Player player = (Player) event.getPlayer(); + final HumanEntity player = event.getPlayer(); - if (!listenerService.shouldCancelEvent(player)) { - return; + if (listenerService.shouldCancelEvent(player)) { + event.setCancelled(true); + + /* + * @note little hack cause InventoryOpenEvent cannot be cancelled for + * real, cause no packet is sent to server by client for the main inv + */ + bukkitService.scheduleSyncDelayedTask(player::closeInventory, 1); } - event.setCancelled(true); - - /* - * @note little hack cause InventoryOpenEvent cannot be cancelled for - * real, cause no packet is send to server by client for the main inv - */ - bukkitService.scheduleSyncDelayedTask(player::closeInventory, 1); } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index bdae313ac..b48f5efc7 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -16,14 +16,14 @@ public final class ReflectionTestUtils { } /** - * Set the field of a given object to a new value with reflection. + * Sets the field of a given object to a new value with reflection. * - * @param clazz The class of the object - * @param instance The instance to modify (pass null for static fields) - * @param fieldName The field name - * @param value The value to set the field to + * @param clazz the class declaring the field + * @param instance the instance to modify (pass null for static fields) + * @param fieldName the field name + * @param value the value to set the field to */ - public static void setField(Class clazz, T instance, String fieldName, Object value) { + public static void setField(Class clazz, T instance, String fieldName, Object value) { try { Field field = getField(clazz, fieldName); field.set(instance, value); @@ -34,6 +34,18 @@ public final class ReflectionTestUtils { } } + /** + * Sets the field on the given instance to the new value. + * + * @param instance the instance to modify + * @param fieldName the field name + * @param value the value to set the field to + */ + @SuppressWarnings("unchecked") + public static void setField(Object instance, String fieldName, Object value) { + setField((Class) instance.getClass(), instance, fieldName, value); + } + private static Field getField(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); @@ -62,13 +74,13 @@ public final class ReflectionTestUtils { } /** - * Return the method on the given class with the supplied parameter types. + * Returns the method on the given class with the supplied parameter types. * - * @param clazz The class to retrieve a method from - * @param methodName The name of the method - * @param parameterTypes The parameter types the method to retrieve has + * @param clazz the class to retrieve a method from + * @param methodName the name of the method + * @param parameterTypes the parameter types the method to retrieve has * - * @return The method of the class, set to be accessible + * @return the method of the class, set to be accessible */ public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) { try { diff --git a/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java b/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java index f55f59f8c..f2fd4b0a7 100644 --- a/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/EntityListenerTest.java @@ -1,5 +1,6 @@ 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; @@ -19,6 +20,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static fr.xephi.authme.listener.EventCancelVerifier.withServiceMock; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -194,4 +197,23 @@ public class EntityListenerTest { verify(listenerService).shouldCancelEvent(player); verify(event).setCancelled(true); } + + @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); + ProjectileLaunchEvent event = new ProjectileLaunchEvent(projectile); + given(listenerService.shouldCancelEvent(shooter)).willReturn(true); + + // when + listener.onProjectileLaunch(event); + + // then + verify(listenerService).shouldCancelEvent(shooter); + assertThat(event.isCancelled(), equalTo(true)); + } } diff --git a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java index bc202cfbc..b6b04f743 100644 --- a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.listener; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; @@ -7,17 +8,23 @@ import fr.xephi.authme.message.Messages; 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.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; +import org.bukkit.event.block.SignChangeEvent; 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.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; @@ -31,7 +38,9 @@ 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.PlayerShearEntityEvent; +import org.bukkit.inventory.InventoryView; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -49,9 +58,11 @@ import static fr.xephi.authme.listener.EventCancelVerifier.withServiceMock; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; 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.BDDMockito.given; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doThrow; @@ -95,6 +106,8 @@ public class PlayerListenerTest { private TeleportationService teleportationService; @Mock private ValidationService validationService; + @Mock + private JoinMessageService joinMessageService; /** * #831: If a player is kicked because of "logged in from another location", the kick @@ -596,6 +609,236 @@ public class PlayerListenerTest { verify(event).setResult(PlayerLoginEvent.Result.KICK_OTHER); } + @Test + public void shouldRemoveMessageOnQuit() { + // given + given(settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)).willReturn(true); + given(antiBotService.wasPlayerKicked(anyString())).willReturn(false); + Player player = mockPlayerWithName("Billy"); + PlayerQuitEvent event = new PlayerQuitEvent(player, "Player has quit the server"); + + // when + listener.onPlayerQuit(event); + + // then + assertThat(event.getQuitMessage(), nullValue()); + verify(antiBotService).wasPlayerKicked("Billy"); + verify(management).performQuit(player); + } + + @Test + public void shouldRemoveMessageForUnloggedUser() { + // given + given(settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)).willReturn(false); + given(settings.getProperty(RegistrationSettings.REMOVE_UNLOGGED_LEAVE_MESSAGE)).willReturn(true); + String name = "Joel"; + given(antiBotService.wasPlayerKicked(name)).willReturn(true); + Player player = mockPlayerWithName(name); + PlayerQuitEvent event = new PlayerQuitEvent(player, "Joel exits the party"); + given(listenerService.shouldCancelEvent(event)).willReturn(true); + + // when + listener.onPlayerQuit(event); + + // then + assertThat(event.getQuitMessage(), nullValue()); + verify(antiBotService).wasPlayerKicked(name); + verifyZeroInteractions(management); + } + + @Test + public void shouldProcessPlayerAndKeepQuitMessage() { + // given + String name = "Louis"; + Player player = mockPlayerWithName(name); + given(settings.getProperty(RegistrationSettings.REMOVE_LEAVE_MESSAGE)).willReturn(false); + given(settings.getProperty(RegistrationSettings.REMOVE_UNLOGGED_LEAVE_MESSAGE)).willReturn(false); + given(antiBotService.wasPlayerKicked(name)).willReturn(false); + String quitMessage = "The player has left the server."; + PlayerQuitEvent event = new PlayerQuitEvent(player, quitMessage); + + // when + listener.onPlayerQuit(event); + + // then + assertThat(event.getQuitMessage(), equalTo(quitMessage)); + verify(antiBotService).wasPlayerKicked(name); + verify(management).performQuit(player); + } + + @Test + public void shouldCancelInventoryClickEvent() { + // given + InventoryClickEvent event = mock(InventoryClickEvent.class); + HumanEntity player = mock(Player.class); + given(event.getWhoClicked()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(true); + + // when + listener.onPlayerInventoryClick(event); + + // then + verify(event).setCancelled(true); + } + + @Test + public void shouldAllowInventoryClickEvent() { + // given + InventoryClickEvent event = mock(InventoryClickEvent.class); + HumanEntity player = mock(Player.class); + given(event.getWhoClicked()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(false); + + // when + listener.onPlayerInventoryClick(event); + + // then + verify(event, only()).getWhoClicked(); + } + + @Test + public void shouldAllowSignChangeEvent() { + // given + SignChangeEvent event = mock(SignChangeEvent.class); + Player player = mock(Player.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(false); + + // when + listener.onSignChange(event); + + // then + verify(event, only()).getPlayer(); + } + + @Test + public void shouldCancelSignChangeEvent() { + // given + SignChangeEvent event = mock(SignChangeEvent.class); + Player player = mock(Player.class); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(true); + + // when + listener.onSignChange(event); + + // then + verify(event).setCancelled(true); + } + + @Test + public void shouldAllowInventoryOpen() { + // given + HumanEntity player = mock(Player.class); + InventoryView transaction = mock(InventoryView.class); + given(transaction.getPlayer()).willReturn(player); + InventoryOpenEvent event = new InventoryOpenEvent(transaction); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(false); + + // when + listener.onPlayerInventoryOpen(event); + + // then + assertThat(event.isCancelled(), equalTo(false)); + verifyZeroInteractions(bukkitService); + } + + @Test + public void shouldCancelInventoryOpen() { + // given + HumanEntity player = mock(Player.class); + InventoryView transaction = mock(InventoryView.class); + given(transaction.getPlayer()).willReturn(player); + InventoryOpenEvent event = new InventoryOpenEvent(transaction); + given(event.getPlayer()).willReturn(player); + given(listenerService.shouldCancelEvent(player)).willReturn(true); + + // when + listener.onPlayerInventoryOpen(event); + + // then + assertThat(event.isCancelled(), equalTo(true)); + TestHelper.runSyncDelayedTaskWithDelay(bukkitService); + verify(player).closeInventory(); + } + + @Test + public void shouldNotModifyJoinMessage() { + // given + Player player = mock(Player.class); + String joinMsg = "The player joined"; + PlayerJoinEvent event = new PlayerJoinEvent(player, joinMsg); + given(settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)).willReturn(false); + given(settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE)).willReturn(""); + given(settings.getProperty(RegistrationSettings.DELAY_JOIN_MESSAGE)).willReturn(false); + + // when + listener.onJoinMessage(event); + + // then + assertThat(event.getJoinMessage(), equalTo(joinMsg)); + verifyZeroInteractions(joinMessageService); + } + + @Test + public void shouldRemoveJoinMessage() { + // given + Player player = mock(Player.class); + String joinMsg = "The player joined"; + PlayerJoinEvent event = new PlayerJoinEvent(player, joinMsg); + given(settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)).willReturn(true); + + // when + listener.onJoinMessage(event); + + // then + assertThat(event.getJoinMessage(), nullValue()); + verifyZeroInteractions(joinMessageService); + } + + @Test + public void shouldUseCustomMessage() { + // given + Player player = mock(Player.class); + given(player.getName()).willReturn("doooew"); + given(player.getDisplayName()).willReturn("Displ"); + String joinMsg = "The player joined"; + PlayerJoinEvent event = new PlayerJoinEvent(player, joinMsg); + given(settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)).willReturn(false); + given(settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE)) + .willReturn("Hello {PLAYERNAME} (aka {DISPLAYNAME})"); + given(settings.getProperty(RegistrationSettings.DELAY_JOIN_MESSAGE)).willReturn(false); + + // when + listener.onJoinMessage(event); + + // then + assertThat(event.getJoinMessage(), equalTo("Hello doooew (aka Displ)")); + verifyZeroInteractions(joinMessageService); + } + + @Test + public void shouldDelayJoinMessage() { + // given + Player player = mock(Player.class); + given(player.getName()).willReturn("thename0"); + given(player.getDisplayName()).willReturn("(not used)"); + String joinMsg = "The player joined"; + PlayerJoinEvent event = new PlayerJoinEvent(player, joinMsg); + given(settings.getProperty(RegistrationSettings.REMOVE_JOIN_MESSAGE)).willReturn(false); + given(settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE)) + .willReturn("{PLAYERNAME} is joining us"); + given(settings.getProperty(RegistrationSettings.DELAY_JOIN_MESSAGE)).willReturn(true); + + // when + listener.onJoinMessage(event); + + // then + assertThat(event.getJoinMessage(), nullValue()); + verify(joinMessageService).putMessage("thename0", "thename0 is joining us"); + } + private static Player mockPlayerWithName(String name) { Player player = mock(Player.class); given(player.getName()).willReturn(name);