diff --git a/.gitignore b/.gitignore index da292ac5..9a8a9ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ hs_err_pid* # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm # Ignore project files *.iml +*.java___jb_tmp___ # Ignore IDEA directory .idea/* diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index 1a7bc577..38b7c50b 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -64,16 +64,16 @@ public class OnJoinVerifier implements Reloadable { /** * Checks if Antibot is enabled. * - * @param player the player + * @param name the player name * @param isAuthAvailable whether or not the player is registered * @throws FailedVerificationException if the verification fails */ - public void checkAntibot(Player player, boolean isAuthAvailable) throws FailedVerificationException { - if (isAuthAvailable || permissionsManager.hasPermission(player, 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(player.getName()); + antiBotService.addPlayerKick(name); throw new FailedVerificationException(MessageKey.KICK_ANTIBOT); } } @@ -148,14 +148,13 @@ public class OnJoinVerifier implements Reloadable { /** * Checks that the casing in the username corresponds to the one in the database, if so configured. * - * @param player the player to verify + * @param connectingName the player name to verify * @param auth the auth object associated with the player * @throws FailedVerificationException if the verification fails */ - public void checkNameCasing(Player player, PlayerAuth auth) throws FailedVerificationException { + public void checkNameCasing(String connectingName, 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); @@ -168,15 +167,15 @@ public class OnJoinVerifier implements Reloadable { /** * Checks that the player's country is admitted. * - * @param player the player + * @param name the player name * @param address the player address * @param isAuthAvailable whether or not the user is registered * @throws FailedVerificationException if the verification fails */ - public void checkPlayerCountry(Player player, String address, boolean isAuthAvailable) throws FailedVerificationException { + 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(player, 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 e77bf0cb..e671d1bf 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -12,6 +12,7 @@ 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.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -26,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; @@ -77,6 +79,8 @@ public class PlayerListener implements Listener { @Inject private JoinMessageService joinMessageService; + private boolean isAsyncPlayerPreLoginEventCalled = false; + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { String cmd = event.getMessage().split(" ")[0].toLowerCase(); @@ -190,19 +194,54 @@ public class PlayerListener implements Listener { management.performJoin(player); } + private void runOnJoinChecks(String name, String ip) throws FailedVerificationException { + // Fast stuff + 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(name, isAuthAvailable); + onJoinVerifier.checkNameCasing(name, auth); + onJoinVerifier.checkPlayerCountry(name, 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; + + final String name = event.getName(); + + if (validationService.isUnrestricted(name)) { + return; + } + + try { + runOnJoinChecks(name, event.getAddress().getHostAddress()); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + } + } + @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; } @@ -210,26 +249,16 @@ public class PlayerListener implements Listener { return; } - try { - // Fast stuff - 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(player, isAuthAvailable); - onJoinVerifier.checkNameCasing(player, auth); - onJoinVerifier.checkPlayerCountry(player, event.getAddress().getHostAddress(), isAuthAvailable); - } catch (FailedVerificationException e) { - event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); - event.setResult(PlayerLoginEvent.Result.KICK_OTHER); - return; - } - teleportationService.teleportOnJoin(player); + + if(!isAsyncPlayerPreLoginEventCalled) { + try { + runOnJoinChecks(name, event.getAddress().getHostAddress()); + } catch (FailedVerificationException e) { + event.setKickMessage(m.retrieveSingle(e.getReason(), e.getArgs())); + event.setResult(PlayerLoginEvent.Result.KICK_OTHER); + } + } } @EventHandler(priority = EventPriority.HIGHEST) diff --git a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java index 29ccd83a..92eac728 100644 --- a/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/listener/ListenerConsistencyTest.java @@ -31,7 +31,7 @@ public final class ListenerConsistencyTest { "PlayerListener#onPlayerJoin", "PlayerListener#onPlayerLogin", "PlayerListener#onPlayerQuit", "ServerListener#onPluginDisable", "ServerListener#onServerPing", "ServerListener#onPluginEnable", - "PlayerListener#onJoinMessage"); + "PlayerListener#onJoinMessage", "PlayerListener#onAsyncPlayerPreLoginEvent"); @BeforeClass public static void collectListenerClasses() { diff --git a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java index 4aa7acce..e96a9199 100644 --- a/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java +++ b/src/test/java/fr/xephi/authme/listener/OnJoinVerifierTest.java @@ -248,12 +248,12 @@ public class OnJoinVerifierTest { @Test public void shouldAllowProperlyCasedName() throws FailedVerificationException { // given - Player player = newPlayerWithName("Bobby"); + String name = "Bobby"; PlayerAuth auth = PlayerAuth.builder().name("bobby").realName("Bobby").build(); given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); // when - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); // then verifyZeroInteractions(dataSource); @@ -262,7 +262,7 @@ public class OnJoinVerifierTest { @Test public void shouldRejectNameWithWrongCasing() throws FailedVerificationException { // given - Player player = newPlayerWithName("Tester"); + String name = "Tester"; PlayerAuth auth = PlayerAuth.builder().name("tester").realName("testeR").build(); given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); @@ -270,19 +270,19 @@ public class OnJoinVerifierTest { expectValidationExceptionWith(MessageKey.INVALID_NAME_CASE, "testeR", "Tester"); // when / then - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); verifyZeroInteractions(dataSource); } @Test public void shouldUpdateMissingRealName() throws FailedVerificationException { // given - Player player = newPlayerWithName("Authme"); + String name = "Authme"; PlayerAuth auth = PlayerAuth.builder().name("authme").realName("").build(); given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); // when - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); // then verify(dataSource).updateRealName("authme", "Authme"); @@ -291,12 +291,12 @@ public class OnJoinVerifierTest { @Test public void shouldUpdateDefaultRealName() throws FailedVerificationException { // given - Player player = newPlayerWithName("SOMEONE"); + String name = "SOMEONE"; PlayerAuth auth = PlayerAuth.builder().name("someone").realName("Player").build(); given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(true); // when - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); // then verify(dataSource).updateRealName("someone", "SOMEONE"); @@ -305,12 +305,12 @@ public class OnJoinVerifierTest { @Test public void shouldAcceptCasingMismatchForDisabledSetting() throws FailedVerificationException { // given - Player player = newPlayerWithName("Test"); + String name = "Test"; PlayerAuth auth = PlayerAuth.builder().name("test").realName("TEST").build(); given(settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE)).willReturn(false); // when - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); // then verifyZeroInteractions(dataSource); @@ -319,11 +319,11 @@ public class OnJoinVerifierTest { @Test public void shouldAcceptNameForUnregisteredAccount() throws FailedVerificationException { // given - Player player = newPlayerWithName("MyPlayer"); + String name = "MyPlayer"; PlayerAuth auth = null; // when - onJoinVerifier.checkNameCasing(player, auth); + onJoinVerifier.checkNameCasing(name, auth); // then verifyZeroInteractions(dataSource); @@ -347,7 +347,9 @@ public class OnJoinVerifierTest { public void shouldRejectNameAlreadyOnline() throws FailedVerificationException { // given String name = "Charlie"; - Player onlinePlayer = newPlayerWithName("charlie"); + + Player onlinePlayer = mock(Player.class); + given(bukkitService.getPlayerExact("Charlie")).willReturn(onlinePlayer); given(settings.getProperty(RestrictionSettings.FORCE_SINGLE_SESSION)).willReturn(true); @@ -374,27 +376,27 @@ public class OnJoinVerifierTest { @Test public void shouldAllowUser() throws FailedVerificationException { // given - Player player = newPlayerWithName("Bobby"); + String name = "Bobby"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); given(antiBotService.shouldKick()).willReturn(false); // when - onJoinVerifier.checkAntibot(player, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then - verify(permissionsManager).hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verify(antiBotService).shouldKick(); } @Test public void shouldAllowUserWithAuth() throws FailedVerificationException { // given - Player player = newPlayerWithName("Lacey"); + String name = "Lacey"; boolean isAuthAvailable = true; // when - onJoinVerifier.checkAntibot(player, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then verifyZeroInteractions(permissionsManager, antiBotService); @@ -403,32 +405,32 @@ public class OnJoinVerifierTest { @Test public void shouldAllowUserWithBypassPermission() throws FailedVerificationException { // given - Player player = newPlayerWithName("Steward"); + String name = "Steward"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(true); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(true); // when - onJoinVerifier.checkAntibot(player, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); // then - verify(permissionsManager).hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verifyZeroInteractions(antiBotService); } @Test public void shouldKickUserForFailedAntibotCheck() throws FailedVerificationException { // given - Player player = newPlayerWithName("D3"); + String name = "D3"; boolean isAuthAvailable = false; - given(permissionsManager.hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); + given(permissionsManager.hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT)).willReturn(false); given(antiBotService.shouldKick()).willReturn(true); // when / then try { - onJoinVerifier.checkAntibot(player, isAuthAvailable); + onJoinVerifier.checkAntibot(name, isAuthAvailable); fail("Expected exception to be thrown"); } catch (FailedVerificationException e) { - verify(permissionsManager).hasPermission(player, PlayerStatePermission.BYPASS_ANTIBOT); + verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.BYPASS_ANTIBOT); verify(antiBotService).shouldKick(); } @@ -439,18 +441,18 @@ public class OnJoinVerifierTest { */ @Test public void shouldNotCheckCountry() throws FailedVerificationException { - Player player = newPlayerWithName("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(player, 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(player, ip, true); + onJoinVerifier.checkPlayerCountry(name, ip, true); verifyZeroInteractions(validationService); } @@ -458,12 +460,12 @@ public class OnJoinVerifierTest { public void shouldCheckAndAcceptUnregisteredPlayerCountry() throws FailedVerificationException { // given String ip = "192.168.0.1"; - Player player = newPlayerWithName("lucas"); + String name = "lucas"; given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(true); given(validationService.isCountryAdmitted(ip)).willReturn(true); // when - onJoinVerifier.checkPlayerCountry(player, ip, false); + onJoinVerifier.checkPlayerCountry(name, ip, false); // then verify(validationService).isCountryAdmitted(ip); @@ -473,13 +475,13 @@ public class OnJoinVerifierTest { public void shouldCheckAndAcceptRegisteredPlayerCountry() throws FailedVerificationException { // given String ip = "192.168.10.24"; - Player player = newPlayerWithName("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(player, ip, true); + onJoinVerifier.checkPlayerCountry(name, ip, true); // then verify(validationService).isCountryAdmitted(ip); @@ -489,7 +491,7 @@ public class OnJoinVerifierTest { public void shouldThrowForBannedCountry() throws FailedVerificationException { // given String ip = "192.168.40.0"; - Player player = newPlayerWithName("bob"); + String name = "bob"; given(settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)).willReturn(true); given(validationService.isCountryAdmitted(ip)).willReturn(false); @@ -497,13 +499,7 @@ public class OnJoinVerifierTest { expectValidationExceptionWith(MessageKey.COUNTRY_BANNED_ERROR); // when - onJoinVerifier.checkPlayerCountry(player, ip, false); - } - - private static Player newPlayerWithName(String name) { - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - return player; + onJoinVerifier.checkPlayerCountry(name, ip, false); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java index 13ec2f69..80d6c974 100644 --- a/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java +++ b/src/test/java/fr/xephi/authme/listener/PlayerListenerTest.java @@ -575,10 +575,10 @@ public class PlayerListenerTest { verify(onJoinVerifier).refusePlayerForFullServer(event); verify(onJoinVerifier).checkSingleSession(name); verify(onJoinVerifier).checkIsValidName(name); - verify(onJoinVerifier).checkAntibot(player, true); + verify(onJoinVerifier).checkAntibot(name, true); verify(onJoinVerifier).checkKickNonRegistered(true); - verify(onJoinVerifier).checkNameCasing(player, auth); - verify(onJoinVerifier).checkPlayerCountry(player, ip, true); + verify(onJoinVerifier).checkNameCasing(name, auth); + verify(onJoinVerifier).checkPlayerCountry(name, ip, true); verify(teleportationService).teleportOnJoin(player); verifyNoModifyingCalls(event); } @@ -588,7 +588,8 @@ public class PlayerListenerTest { // given String name = "inval!dName"; Player player = mockPlayerWithName(name); - PlayerLoginEvent event = spy(new PlayerLoginEvent(player, "", null)); + TestHelper.mockPlayerIp(player, "33.32.33.33"); + PlayerLoginEvent event = spy(new PlayerLoginEvent(player, "", player.getAddress().getAddress())); given(validationService.isUnrestricted(name)).willReturn(false); given(onJoinVerifier.refusePlayerForFullServer(event)).willReturn(false); FailedVerificationException exception = new FailedVerificationException(