diff --git a/pom.xml b/pom.xml index a9398738..f4041180 100644 --- a/pom.xml +++ b/pom.xml @@ -436,6 +436,14 @@ true + + + org.spigotmc + spigot-api + 1.9.2-R0.1-SNAPSHOT + provided + + org.bukkit diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 626ed2a6..647484cd 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -26,6 +26,7 @@ import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.listener.AuthMePlayerListener16; import fr.xephi.authme.listener.AuthMePlayerListener18; +import fr.xephi.authme.listener.AuthMePlayerListener19; import fr.xephi.authme.listener.AuthMeServerListener; import fr.xephi.authme.listener.AuthMeTabCompletePacketAdapter; import fr.xephi.authme.listener.AuthMeTablistPacketAdapter; @@ -392,6 +393,13 @@ public class AuthMe extends JavaPlugin { pluginManager.registerEvents(initializer.get(AuthMePlayerListener18.class), this); } catch (ClassNotFoundException ignore) { } + + // Try to register 1.9 player listeners + try { + Class.forName("org.spigotmc.event.player.PlayerSpawnLocationEvent"); + pluginManager.registerEvents(initializer.get(AuthMePlayerListener19.class), this); + } catch (ClassNotFoundException ignore) { + } } private void reloadSupportHook() { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index d61ea002..d57581ff 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -21,7 +21,7 @@ public class RegisterAdminCommand implements ExecutableCommand { @Inject private PasswordSecurity passwordSecurity; - + @Inject private DataSource dataSource; @@ -65,7 +65,13 @@ public class RegisterAdminCommand implements ExecutableCommand { ConsoleLogger.info(sender.getName() + " registered " + playerName); Player player = commandService.getPlayer(playerName); if (player != null) { - player.kickPlayer("An admin just registered you, please log in again"); + final Player p = player; + p.getServer().getScheduler().scheduleSyncDelayedTask(commandService.getAuthMe(), new Runnable() { + @Override + public void run() { + p.kickPlayer("An admin just registered you, please log in again"); + } + }); } } }); diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 97383c53..a0fa4011 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -20,11 +20,12 @@ import fr.xephi.authme.settings.NewSetting; 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.ProtectionSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.BukkitService; -import fr.xephi.authme.util.GeoLiteAPI; import fr.xephi.authme.util.Utils; +import fr.xephi.authme.util.ValidationService; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; @@ -85,6 +86,8 @@ public class AuthMePlayerListener implements Listener { private BukkitService bukkitService; @Inject private SpawnLoader spawnLoader; + @Inject + private ValidationService validationService; private void handleChat(AsyncPlayerChatEvent event) { if (settings.getProperty(RestrictionSettings.ALLOW_CHAT)) { @@ -265,30 +268,22 @@ public class AuthMePlayerListener implements Listener { PlayerAuth auth = dataSource.getAuth(event.getName()); if (settings.getProperty(RegistrationSettings.PREVENT_OTHER_CASE) && auth != null && auth.getRealName() != null) { String realName = auth.getRealName(); - if (!realName.isEmpty() && !realName.equals("Player") && !realName.equals(event.getName())) { + if (!realName.isEmpty() && !"Player".equals(realName) && !realName.equals(event.getName())) { event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); event.setKickMessage(m.retrieveSingle(MessageKey.INVALID_NAME_CASE, realName, event.getName())); return; } - if (realName.isEmpty() || realName.equals("Player")) { + if (realName.isEmpty() || "Player".equals(realName)) { dataSource.updateRealName(event.getName().toLowerCase(), event.getName()); } } - if (auth == null) { - if (!Settings.countriesBlacklist.isEmpty() || !Settings.countries.isEmpty()) { - String playerIP = event.getAddress().getHostAddress(); - String countryCode = GeoLiteAPI.getCountryCode(playerIP); - if (Settings.countriesBlacklist.contains(countryCode)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } - if (Settings.enableProtection && !Settings.countries.contains(countryCode)) { - event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); - event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } + if (auth == null && settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + event.setKickMessage(m.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); + return; } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java new file mode 100644 index 00000000..8be48ba2 --- /dev/null +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener19.java @@ -0,0 +1,27 @@ +package fr.xephi.authme.listener; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.spigotmc.event.player.PlayerSpawnLocationEvent; + +import fr.xephi.authme.AuthMe; + +import javax.inject.Inject; + +/** + * Listener of player events for events introduced in Minecraft 1.9. + */ +public class AuthMePlayerListener19 implements Listener { + + @Inject + private AuthMe plugin; + + public AuthMePlayerListener19() { } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerSpawn(PlayerSpawnLocationEvent event) { + event.setSpawnLocation(plugin.getSpawnLocation(event.getPlayer())); + } + +} diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java index 54df067e..bb85374d 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeServerListener.java @@ -5,9 +5,10 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.hooks.PluginHooks; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.SpawnLoader; -import fr.xephi.authme.util.GeoLiteAPI; +import fr.xephi.authme.settings.properties.ProtectionSettings; +import fr.xephi.authme.util.ValidationService; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -26,19 +27,19 @@ public class AuthMeServerListener implements Listener { @Inject private Messages messages; @Inject + private NewSetting settings; + @Inject private PluginHooks pluginHooks; @Inject private SpawnLoader spawnLoader; + @Inject + private ValidationService validationService; @EventHandler(priority = EventPriority.HIGHEST) public void onServerPing(ServerListPingEvent event) { - if (!Settings.countriesBlacklist.isEmpty() || !Settings.countries.isEmpty()){ - String countryCode = GeoLiteAPI.getCountryCode(event.getAddress().getHostAddress()); - if( Settings.countriesBlacklist.contains(countryCode)) { - event.setMotd(messages.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); - return; - } - if (Settings.enableProtection && !Settings.countries.contains(countryCode)) { + if (settings.getProperty(ProtectionSettings.ENABLE_PROTECTION)) { + String playerIp = event.getAddress().getHostAddress(); + if (!validationService.isCountryAdmitted(playerIp)) { event.setMotd(messages.retrieveSingle(MessageKey.COUNTRY_BANNED_ERROR)); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java index d13668d5..17b4a60a 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMeTablistPacketAdapter.java @@ -8,6 +8,7 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode; import com.comphenix.protocol.wrappers.EnumWrappers.PlayerInfoAction; import com.comphenix.protocol.wrappers.PlayerInfoData; @@ -82,7 +83,12 @@ public class AuthMeTablistPacketAdapter extends PacketAdapter { } public void register() { - ProtocolLibrary.getProtocolManager().addPacketListener(this); + if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) { + ProtocolLibrary.getProtocolManager().addPacketListener(this); + } else { + ConsoleLogger.info("The hideTablist feature is not compatible with your minecraft version"); + ConsoleLogger.info("It requires 1.8+. Disabling the hideTablist feature..."); + } } public void unregister() { diff --git a/src/main/java/fr/xephi/authme/util/ValidationService.java b/src/main/java/fr/xephi/authme/util/ValidationService.java index 815f493f..999e2d1c 100644 --- a/src/main/java/fr/xephi/authme/util/ValidationService.java +++ b/src/main/java/fr/xephi/authme/util/ValidationService.java @@ -5,7 +5,9 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.domain.Property; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.CommandSender; @@ -54,26 +56,74 @@ public class ValidationService { return null; } + /** + * Verifies whether the email is valid and admitted for use according to the plugin settings. + * + * @param email the email to verify + * @return true if the email is valid, false otherwise + */ public boolean validateEmail(String email) { if (!email.contains("@") || "your@email.com".equalsIgnoreCase(email)) { return false; } final String emailDomain = email.split("@")[1]; - - List whitelist = settings.getProperty(EmailSettings.DOMAIN_WHITELIST); - if (!CollectionUtils.isEmpty(whitelist)) { - return containsIgnoreCase(whitelist, emailDomain); - } - - List blacklist = settings.getProperty(EmailSettings.DOMAIN_BLACKLIST); - return CollectionUtils.isEmpty(blacklist) || !containsIgnoreCase(blacklist, emailDomain); + return validateWhitelistAndBlacklist( + emailDomain, EmailSettings.DOMAIN_WHITELIST, EmailSettings.DOMAIN_BLACKLIST); } + /** + * Queries the database whether the email is still free for registration, i.e. whether the given + * command sender may use the email to register a new account (as defined by settings and permissions). + * + * @param email the email to verify + * @param sender the command sender + * @return true if the email may be used, false otherwise (registration threshold has been exceeded) + */ public boolean isEmailFreeForRegistration(String email, CommandSender sender) { return permissionsManager.hasPermission(sender, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) || dataSource.countAuthsByEmail(email) < settings.getProperty(EmailSettings.MAX_REG_PER_EMAIL); } + /** + * Checks whether the player's country is allowed to join the server, based on the given IP address + * and the configured country whitelist or blacklist. + * + * @param hostAddress the IP address to verify + * @return true if the IP address' country is allowed, false otherwise + */ + public boolean isCountryAdmitted(String hostAddress) { + // Check if we have restrictions on country, if not return true and avoid the country lookup + if (settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST).isEmpty() + && settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST).isEmpty()) { + return true; + } + + String countryCode = GeoLiteAPI.getCountryCode(hostAddress); + return validateWhitelistAndBlacklist(countryCode, + ProtectionSettings.COUNTRIES_WHITELIST, + ProtectionSettings.COUNTRIES_BLACKLIST); + } + + /** + * Verifies whether the given value is allowed according to the given whitelist and blacklist settings. + * Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present + * in the whitelist. + * + * @param value the value to verify + * @param whitelist the whitelist property + * @param blacklist the blacklist property + * @return true if the value is admitted by the lists, false otherwise + */ + private boolean validateWhitelistAndBlacklist(String value, Property> whitelist, + Property> blacklist) { + List whitelistValue = settings.getProperty(whitelist); + if (!CollectionUtils.isEmpty(whitelistValue)) { + return containsIgnoreCase(whitelistValue, value); + } + List blacklistValue = settings.getProperty(blacklist); + return CollectionUtils.isEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value); + } + private static boolean containsIgnoreCase(Collection coll, String needle) { for (String entry : coll) { if (entry.equalsIgnoreCase(needle)) { diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java index 3ce93fcf..89fc3da6 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java @@ -8,7 +8,6 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,13 +18,10 @@ import org.mockito.runners.MockitoJUnitRunner; import java.util.Arrays; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -147,8 +143,6 @@ public class RegisterAdminCommandTest { given(dataSource.saveAuth(any(PlayerAuth.class))).willReturn(true); HashedPassword hashedPassword = new HashedPassword("$aea2345EW235dfsa@#R%987048"); given(passwordSecurity.computeHash(password, user)).willReturn(hashedPassword); - Player player = mock(Player.class); - given(commandService.getPlayer(user)).willReturn(player); // when command.executeCommand(sender, Arrays.asList(user, password), commandService); @@ -161,7 +155,6 @@ public class RegisterAdminCommandTest { verify(dataSource).saveAuth(captor.capture()); assertAuthHasInfo(captor.getValue(), user, hashedPassword); verify(dataSource).setUnlogged(user); - verify(player).kickPlayer(argThat(containsString("please log in again"))); } private void assertAuthHasInfo(PlayerAuth auth, String name, HashedPassword hashedPassword) {