From 8aa1bb993567f42943148f50b66514f3c5669e60 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Jan 2017 14:14:32 +0100 Subject: [PATCH 01/79] Open 5.3 development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0648d981..37e5f505 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ fr.xephi authme - 5.2-SNAPSHOT + 5.3-SNAPSHOT AuthMeReloaded The first authentication plugin for the Bukkit API! From 666e2422121869a036a8b42e97af38549380dd53 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Jan 2017 14:47:18 +0100 Subject: [PATCH 02/79] Delete CollectionUtils (unused) --- .../authme/command/CommandDescription.java | 4 +- .../xephi/authme/command/CommandMapper.java | 6 +- .../datasource/converter/xAuthConverter.java | 4 +- .../authme/service/ValidationService.java | 5 +- .../xephi/authme/task/purge/PurgeService.java | 6 +- .../fr/xephi/authme/util/CollectionUtils.java | 63 ----------- src/main/java/fr/xephi/authme/util/Utils.java | 11 ++ .../authme/util/CollectionUtilsTest.java | 102 ------------------ .../fr/xephi/authme/util/FileUtilsTest.java | 5 + 9 files changed, 28 insertions(+), 178 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/util/CollectionUtils.java delete mode 100644 src/test/java/fr/xephi/authme/util/CollectionUtilsTest.java diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index b6c006f9..18463bca 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -1,8 +1,8 @@ package fr.xephi.authme.command; import fr.xephi.authme.permission.PermissionNode; -import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.Utils; import java.util.ArrayList; import java.util.List; @@ -224,7 +224,7 @@ public class CommandDescription { * @return The generated CommandDescription object */ public CommandDescription build() { - checkArgument(!CollectionUtils.isEmpty(labels), "Labels may not be empty"); + checkArgument(!Utils.isCollectionEmpty(labels), "Labels may not be empty"); checkArgument(!StringUtils.isEmpty(description), "Description may not be empty"); checkArgument(!StringUtils.isEmpty(detailedDescription), "Detailed description may not be empty"); checkArgument(executableCommand != null, "Executable command must be set"); diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java index 8e2b1bdc..c5cf91c0 100644 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -2,8 +2,8 @@ package fr.xephi.authme.command; import fr.xephi.authme.command.executable.HelpCommand; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import javax.inject.Inject; @@ -45,7 +45,7 @@ public class CommandMapper { * @return The generated {@link FoundCommandResult} */ public FoundCommandResult mapPartsToCommand(CommandSender sender, final List parts) { - if (CollectionUtils.isEmpty(parts)) { + if (Utils.isCollectionEmpty(parts)) { return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND); } @@ -142,7 +142,7 @@ public class CommandMapper { * @return A command if there was a complete match (including proper argument count), null otherwise */ private static CommandDescription getSuitableChild(CommandDescription baseCommand, List parts) { - if (CollectionUtils.isEmpty(parts)) { + if (Utils.isCollectionEmpty(parts)) { return null; } diff --git a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java index 27fa6c2e..af4d5beb 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java @@ -6,7 +6,7 @@ import de.luricos.bukkit.xAuth.xAuth; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.DataFolder; -import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import org.bukkit.plugin.PluginManager; @@ -55,7 +55,7 @@ public class xAuthConverter implements Converter { sender.sendMessage("[AuthMe] xAuth H2 database not found, checking for MySQL or SQLite data..."); } List players = getXAuthPlayers(); - if (CollectionUtils.isEmpty(players)) { + if (Utils.isCollectionEmpty(players)) { sender.sendMessage("[AuthMe] Error while importing xAuthPlayers: did not find any players"); return; } diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index 8ef7b1da..b6e338ec 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -11,7 +11,6 @@ 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 fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; @@ -144,11 +143,11 @@ public class ValidationService implements Reloadable { private boolean validateWhitelistAndBlacklist(String value, Property> whitelist, Property> blacklist) { List whitelistValue = settings.getProperty(whitelist); - if (!CollectionUtils.isEmpty(whitelistValue)) { + if (!Utils.isCollectionEmpty(whitelistValue)) { return containsIgnoreCase(whitelistValue, value); } List blacklistValue = settings.getProperty(blacklist); - return CollectionUtils.isEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value); + return Utils.isCollectionEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value); } private static boolean containsIgnoreCase(Collection coll, String needle) { diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java index 7a1d19fa..fed07768 100644 --- a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java +++ b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java @@ -3,10 +3,10 @@ package fr.xephi.authme.task.purge; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PurgeSettings; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.util.Utils; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -75,7 +75,7 @@ public class PurgeService { public void runPurge(CommandSender sender, long until, boolean includeEntriesWithLastLoginZero) { //todo: note this should may run async because it may executes a SQL-Query Set toPurge = dataSource.getRecordsToPurge(until, includeEntriesWithLastLoginZero); - if (CollectionUtils.isEmpty(toPurge)) { + if (Utils.isCollectionEmpty(toPurge)) { logAndSendMessage(sender, "No players to purge"); return; } diff --git a/src/main/java/fr/xephi/authme/util/CollectionUtils.java b/src/main/java/fr/xephi/authme/util/CollectionUtils.java deleted file mode 100644 index 2388fc60..00000000 --- a/src/main/java/fr/xephi/authme/util/CollectionUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package fr.xephi.authme.util; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Utils class for collections. - */ -public final class CollectionUtils { - - // Utility class - private CollectionUtils() { - } - - /** - * Get a range from a list based on start and count parameters in a safe way. - * - * @param element - * @param list The List - * @param start The start index - * @param count The number of elements to add - * - * @return The sublist consisting at most of {@code count} elements (less if the parameters - * exceed the size of the list) - */ - public static List getRange(List list, int start, int count) { - if (start >= list.size() || count <= 0) { - return new ArrayList<>(); - } else if (start < 0) { - start = 0; - } - int end = Math.min(list.size(), start + count); - return list.subList(start, end); - } - - /** - * Get all elements from a list starting from the given index. - * - * @param element - * @param list The List - * @param start The start index - * - * @return The sublist of all elements from index {@code start} and on; empty list - * if the start index exceeds the list's size - */ - public static List getRange(List list, int start) { - if (start >= list.size()) { - return new ArrayList<>(); - } - return getRange(list, start, list.size() - start); - } - - /** - * Null-safe way to check whether a collection is empty or not. - * - * @param coll The collection to verify - * @return True if the collection is null or empty, false otherwise - */ - public static boolean isEmpty(Collection coll) { - return coll == null || coll.isEmpty(); - } -} diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index b3ec53c7..b7628147 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -2,6 +2,7 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; +import java.util.Collection; import java.util.regex.Pattern; /** @@ -50,6 +51,16 @@ public final class Utils { } } + /** + * Null-safe way to check whether a collection is empty or not. + * + * @param coll The collection to verify + * @return True if the collection is null or empty, false otherwise + */ + public static boolean isCollectionEmpty(Collection coll) { + return coll == null || coll.isEmpty(); + } + /** * Return the available core count of the JVM. * diff --git a/src/test/java/fr/xephi/authme/util/CollectionUtilsTest.java b/src/test/java/fr/xephi/authme/util/CollectionUtilsTest.java deleted file mode 100644 index f359f088..00000000 --- a/src/test/java/fr/xephi/authme/util/CollectionUtilsTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package fr.xephi.authme.util; - -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.hamcrest.Matchers.empty; - -/** - * Test for {@link CollectionUtils}. - */ -public class CollectionUtilsTest { - - @Test - public void shouldGetFullList() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 0, 24); - - // then - assertThat(result, equalTo(list)); - } - - @Test - public void shouldReturnEmptyListForZeroCount() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 2, 0); - - // then - assertThat(result, empty()); - } - - - @Test - public void shouldReturnEmptyListForTooHighStart() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 12, 2); - - // then - assertThat(result, empty()); - } - - @Test - public void shouldReturnSubList() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 1, 3); - - // then - assertThat(result, contains("1", "2", "3")); - } - - @Test - public void shouldReturnTillEnd() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 2, 3); - - // then - assertThat(result, contains("2", "3", "4")); - } - - @Test - public void shouldRemoveFirstTwo() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, 2); - - // then - assertThat(result, contains("2", "3", "4")); - } - - @Test - public void shouldHandleNegativeStart() { - // given - List list = Arrays.asList("test", "1", "2", "3", "4"); - - // when - List result = CollectionUtils.getRange(list, -4); - - // then - assertThat(result, equalTo(list)); - } -} diff --git a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java index fa8ee9c6..9d65047c 100644 --- a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java @@ -137,6 +137,11 @@ public class FileUtilsTest { assertThat(result, equalTo("path" + File.separator + "to" + File.separator + "test-file.txt")); } + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(FileUtils.class); + } + private static void createFiles(File... files) throws IOException { for (File file : files) { boolean result = file.getParentFile().mkdirs() & file.createNewFile(); From 811bdee12831c8f3ae6173d064ad40a82b43ba5b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Jan 2017 13:53:11 +0100 Subject: [PATCH 03/79] #1015 Implement lazy replace of tags for welcome message - Check which tags are used when loading the welcome message and only apply their replacements afterwards - Moves AuthMe#replaceAllInfo to a more appropriate place - Avoid fetching data again for each line --- src/main/java/fr/xephi/authme/AuthMe.java | 26 +--- .../process/login/ProcessSyncPlayerLogin.java | 15 +- .../fr/xephi/authme/settings/Settings.java | 11 -- .../settings/WelcomeMessageConfiguration.java | 143 ++++++++++++++++++ .../fr/xephi/authme/util/lazytags/Tag.java | 53 +++++++ .../xephi/authme/settings/SettingsTest.java | 24 --- .../WelcomeMessageConfigurationTest.java | 106 +++++++++++++ 7 files changed, 311 insertions(+), 67 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/Tag.java create mode 100644 src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 74a98654..1b987080 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -27,7 +27,6 @@ import fr.xephi.authme.permission.PermissionsSystemType; import fr.xephi.authme.security.crypts.SHA256; import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.service.MigrationService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -35,11 +34,9 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.purge.PurgeService; -import fr.xephi.authme.util.PlayerUtils; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; @@ -72,8 +69,6 @@ public class AuthMe extends JavaPlugin { private DataSource database; private BukkitService bukkitService; private Injector injector; - private GeoIpService geoIpService; - private PlayerCache playerCache; /** * Constructor. @@ -242,14 +237,13 @@ public class AuthMe extends JavaPlugin { */ protected void instantiateServices(Injector injector) { // PlayerCache is still injected statically sometimes - playerCache = PlayerCache.getInstance(); + PlayerCache playerCache = PlayerCache.getInstance(); injector.register(PlayerCache.class, playerCache); database = injector.getSingleton(DataSource.class); permsMan = injector.getSingleton(PermissionsManager.class); bukkitService = injector.getSingleton(BukkitService.class); commandHandler = injector.getSingleton(CommandHandler.class); - geoIpService = injector.getSingleton(GeoIpService.class); // Trigger construction of API classes; they will keep track of the singleton injector.getSingleton(NewAPI.class); @@ -344,24 +338,6 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.close(); } - public String replaceAllInfo(String message, Player player) { - String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size()); - String ipAddress = PlayerUtils.getPlayerIp(player); - Server server = getServer(); - return message - .replace("&", "\u00a7") - .replace("{PLAYER}", player.getName()) - .replace("{ONLINE}", playersOnline) - .replace("{MAXPLAYERS}", Integer.toString(server.getMaxPlayers())) - .replace("{IP}", ipAddress) - .replace("{LOGINS}", Integer.toString(playerCache.getLogged())) - .replace("{WORLD}", player.getWorld().getName()) - .replace("{SERVER}", server.getServerName()) - .replace("{VERSION}", server.getBukkitVersion()) - // TODO: We should cache info like this, maybe with a class that extends Player? - .replace("{COUNTRY}", geoIpService.getCountryName(ipAddress)); - } - /** * Handle Bukkit commands. * 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 d64bda77..6fb5fa8b 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -13,15 +13,16 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BungeeService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.WelcomeMessageConfiguration; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.util.StringUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.potion.PotionEffectType; import javax.inject.Inject; +import java.util.List; import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; @@ -54,6 +55,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private Settings settings; + @Inject + private WelcomeMessageConfiguration welcomeMessageConfiguration; + ProcessSyncPlayerLogin() { } @@ -103,15 +107,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { player.saveData(); // Login is done, display welcome message + List welcomeMessage = welcomeMessageConfiguration.getWelcomeMessage(player); if (settings.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { if (settings.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) { - for (String s : settings.getWelcomeMessage()) { - Bukkit.getServer().broadcastMessage(plugin.replaceAllInfo(s, player)); - } + welcomeMessage.forEach(bukkitService::broadcastMessage); } else { - for (String s : settings.getWelcomeMessage()) { - player.sendMessage(plugin.replaceAllInfo(s, player)); - } + welcomeMessage.forEach(player::sendMessage); } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index d287e140..980f2add 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -19,7 +19,6 @@ import static fr.xephi.authme.util.FileUtils.copyFileFromResource; public class Settings extends SettingsManager { private final File pluginFolder; - private String[] welcomeMessage; private String passwordEmailMessage; private String recoveryCodeEmailMessage; @@ -56,19 +55,9 @@ public class Settings extends SettingsManager { return recoveryCodeEmailMessage; } - /** - * Return the lines to output after an in-game registration. - * - * @return The welcome message - */ - public String[] getWelcomeMessage() { - return welcomeMessage; - } - private void loadSettingsFromFiles() { passwordEmailMessage = readFile("email.html"); recoveryCodeEmailMessage = readFile("recovery_code_email.html"); - welcomeMessage = readFile("welcome.txt").split("\\n"); } @Override diff --git a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java new file mode 100644 index 00000000..b2cc9812 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java @@ -0,0 +1,143 @@ +package fr.xephi.authme.settings; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; +import fr.xephi.authme.util.PlayerUtils; +import fr.xephi.authme.util.lazytags.Tag; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import static fr.xephi.authme.util.FileUtils.copyFileFromResource; + +/** + * Configuration for the welcome message (welcome.txt). + */ +public class WelcomeMessageConfiguration implements Reloadable { + + @DataFolder + @Inject + private File pluginFolder; + + @Inject + private Server server; + + @Inject + private GeoIpService geoIpService; + + @Inject + private BukkitService bukkitService; + + @Inject + private PlayerCache playerCache; + + /** List of all supported tags for the welcome message. */ + private final List availableTags = Arrays.asList( + new Tag("&", () -> "\u00a7"), + new Tag("{PLAYER}", pl -> pl.getName()), + new Tag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())), + new Tag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())), + new Tag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)), + new Tag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())), + new Tag("{WORLD}", pl -> pl.getWorld().getName()), + new Tag("{SERVER}", () -> server.getServerName()), + new Tag("{VERSION}", () -> server.getBukkitVersion()), + new Tag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); + + /** Welcome message, by lines. */ + private List welcomeMessage; + /** Tags used in the welcome message. */ + private List usedTags; + + @PostConstruct + @Override + public void reload() { + welcomeMessage = readWelcomeFile(); + usedTags = determineUsedTags(welcomeMessage); + } + + /** + * Returns the welcome message for the given player. + * + * @param player the player for whom the welcome message should be prepared + * @return the welcome message + */ + public List getWelcomeMessage(Player player) { + // Note ljacqu 20170121: Using a Map might seem more natural here but we avoid doing so for performance + // Although the performance gain here is probably minimal... + List tagValues = new LinkedList<>(); + for (Tag tag : usedTags) { + tagValues.add(new TagValue(tag.getName(), tag.getValue(player))); + } + + List adaptedMessages = new LinkedList<>(); + for (String line : welcomeMessage) { + String adaptedLine = line; + for (TagValue tagValue : tagValues) { + adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value); + } + adaptedMessages.add(adaptedLine); + } + return adaptedMessages; + } + + /** + * @return the lines of the welcome message file + */ + private List readWelcomeFile() { + File welcomeFile = new File(pluginFolder, "welcome.txt"); + if (copyFileFromResource(welcomeFile, "welcome.txt")) { + try { + return Files.readAllLines(welcomeFile.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + ConsoleLogger.logException("Failed to read welcome.txt file:", e); + } + } else { + ConsoleLogger.warning("Failed to copy welcome.txt from JAR"); + } + return Collections.emptyList(); + } + + /** + * Determines which tags are used in the message. + * + * @param welcomeMessage the lines of the welcome message + * @return the tags + */ + private List determineUsedTags(List welcomeMessage) { + return availableTags.stream() + .filter(tag -> welcomeMessage.stream().anyMatch(msg -> msg.contains(tag.getName()))) + .collect(Collectors.toList()); + } + + private static final class TagValue { + + private final String tag; + private final String value; + + TagValue(String tag, String value) { + this.tag = tag; + this.value = value; + } + + @Override + public String toString() { + return "TagValue[tag='" + tag + "', value='" + value + "']"; + } + } +} diff --git a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java new file mode 100644 index 00000000..4489f7a7 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java @@ -0,0 +1,53 @@ +package fr.xephi.authme.util.lazytags; + +import org.bukkit.entity.Player; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Represents a tag in a text that can be replaced with data (which may depend on the Player). + */ +public class Tag { + + private final String name; + private final Function replacementFunction; + + /** + * Constructor. + * + * @param name the tag (placeholder) that will be replaced + * @param replacementFunction the function producing the replacement + */ + public Tag(String name, Function replacementFunction) { + this.name = name; + this.replacementFunction = replacementFunction; + } + + /** + * Constructor. + * + * @param name the tag (placeholder) that will be replaced + * @param replacementFunction supplier providing the text to replace the tag with + */ + public Tag(String name, Supplier replacementFunction) { + this(name, p -> replacementFunction.get()); + } + + /** + * @return the tag + */ + public String getName() { + return name; + } + + /** + * Returns the value to replace the tag with for the given player. + * + * @param player the player to evaluate the replacement for + * @return the replacement + */ + public String getValue(Player player) { + return replacementFunction.apply(player); + } +} diff --git a/src/test/java/fr/xephi/authme/settings/SettingsTest.java b/src/test/java/fr/xephi/authme/settings/SettingsTest.java index 42326771..f7d2604c 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsTest.java @@ -4,7 +4,6 @@ import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.configurationdata.ConfigurationDataBuilder; import ch.jalu.configme.resource.PropertyResource; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.TestConfiguration; import org.junit.Before; import org.junit.BeforeClass; @@ -16,11 +15,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** @@ -45,26 +41,6 @@ public class SettingsTest { testPluginFolder = temporaryFolder.newFolder(); } - @Test - public void shouldLoadWelcomeMessage() throws IOException { - // given - String welcomeMessage = "This is my welcome message for testing\nBye!"; - File welcomeFile = new File(testPluginFolder, "welcome.txt"); - createFile(welcomeFile); - Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); - - PropertyResource resource = mock(PropertyResource.class); - given(resource.getBoolean(RegistrationSettings.USE_WELCOME_MESSAGE.getPath())).willReturn(true); - Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); - - // when - String[] result = settings.getWelcomeMessage(); - - // then - assertThat(result, arrayWithSize(2)); - assertThat(result, arrayContaining(welcomeMessage.split("\\n"))); - } - @Test public void shouldLoadEmailMessage() throws IOException { // given diff --git a/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java new file mode 100644 index 00000000..010761a0 --- /dev/null +++ b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java @@ -0,0 +1,106 @@ +package fr.xephi.authme.settings; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link WelcomeMessageConfiguration}. + */ +@RunWith(DelayedInjectionRunner.class) +public class WelcomeMessageConfigurationTest { + + @InjectDelayed + private WelcomeMessageConfiguration welcomeMessageConfiguration; + @Mock + private Server server; + @Mock + private BukkitService bukkitService; + @Mock + private GeoIpService geoIpService; + @Mock + private PlayerCache playerCache; + @DataFolder + private File testPluginFolder; + + private File welcomeFile; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeInjecting + public void createPluginFolder() throws IOException { + testPluginFolder = temporaryFolder.newFolder(); + welcomeFile = new File(testPluginFolder, "welcome.txt"); + welcomeFile.createNewFile(); + } + + @Test + public void shouldLoadWelcomeMessage() throws IOException { + // given + String welcomeMessage = "This is my welcome message for testing\nBye!"; + Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + Player player = mock(Player.class); + welcomeMessageConfiguration.reload(); + + // when + List result = welcomeMessageConfiguration.getWelcomeMessage(player); + + // then + assertThat(result, hasSize(2)); + assertThat(result, contains(welcomeMessage.split("\\n"))); + verifyZeroInteractions(player, playerCache, geoIpService, bukkitService, server); + } + + @Test + public void shouldReplaceNameAndIpAndCountry() throws IOException { + // given + String welcomeMessage = "Hello {PLAYER}, your IP is {IP}\nYour country is {COUNTRY}.\nWelcome to {SERVER}!"; + Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + welcomeMessageConfiguration.reload(); + + Player player = mock(Player.class); + given(player.getName()).willReturn("Bobby"); + TestHelper.mockPlayerIp(player, "123.45.66.77"); + given(geoIpService.getCountryName("123.45.66.77")).willReturn("Syldavia"); + given(server.getServerName()).willReturn("CrazyServer"); + + // when + List result = welcomeMessageConfiguration.getWelcomeMessage(player); + + // then + assertThat(result, hasSize(3)); + assertThat(result.get(0), equalTo("Hello Bobby, your IP is 123.45.66.77")); + assertThat(result.get(1), equalTo("Your country is Syldavia.")); + assertThat(result.get(2), equalTo("Welcome to CrazyServer!")); + verify(server, only()).getServerName(); + verifyZeroInteractions(playerCache); + } +} From 367380265e28c091de5e1535fc44774bc3e8f067 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 22 Jan 2017 10:43:46 +0100 Subject: [PATCH 04/79] #1015 Distinguish player-dependent tags from "simple" tags --- .../settings/WelcomeMessageConfiguration.java | 21 ++++----- .../xephi/authme/util/lazytags/PlayerTag.java | 35 +++++++++++++++ .../xephi/authme/util/lazytags/SimpleTag.java | 29 +++++++++++++ .../fr/xephi/authme/util/lazytags/Tag.java | 41 +++--------------- .../authme/util/lazytags/TagBuilder.java | 23 ++++++++++ .../WelcomeMessageConfigurationTest.java | 43 +++++++++++++++++-- 6 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java diff --git a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java index b2cc9812..8fcb2163 100644 --- a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.stream.Collectors; import static fr.xephi.authme.util.FileUtils.copyFileFromResource; +import static fr.xephi.authme.util.lazytags.TagBuilder.createTag; /** * Configuration for the welcome message (welcome.txt). @@ -48,16 +49,16 @@ public class WelcomeMessageConfiguration implements Reloadable { /** List of all supported tags for the welcome message. */ private final List availableTags = Arrays.asList( - new Tag("&", () -> "\u00a7"), - new Tag("{PLAYER}", pl -> pl.getName()), - new Tag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())), - new Tag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())), - new Tag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)), - new Tag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())), - new Tag("{WORLD}", pl -> pl.getWorld().getName()), - new Tag("{SERVER}", () -> server.getServerName()), - new Tag("{VERSION}", () -> server.getBukkitVersion()), - new Tag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); + createTag("&", () -> "\u00a7"), + createTag("{PLAYER}", pl -> pl.getName()), + createTag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())), + createTag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())), + createTag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)), + createTag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())), + createTag("{WORLD}", pl -> pl.getWorld().getName()), + createTag("{SERVER}", () -> server.getServerName()), + createTag("{VERSION}", () -> server.getBukkitVersion()), + createTag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); /** Welcome message, by lines. */ private List welcomeMessage; diff --git a/src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java b/src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java new file mode 100644 index 00000000..3c476237 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java @@ -0,0 +1,35 @@ +package fr.xephi.authme.util.lazytags; + +import org.bukkit.entity.Player; + +import java.util.function.Function; + +/** + * Replaceable tag whose value depends on the player. + */ +public class PlayerTag implements Tag { + + private final String name; + private final Function replacementFunction; + + /** + * Constructor. + * + * @param name the tag (placeholder) that will be replaced + * @param replacementFunction the function producing the replacement + */ + public PlayerTag(String name, Function replacementFunction) { + this.name = name; + this.replacementFunction = replacementFunction; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue(Player player) { + return replacementFunction.apply(player); + } +} diff --git a/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java new file mode 100644 index 00000000..08ece1c3 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java @@ -0,0 +1,29 @@ +package fr.xephi.authme.util.lazytags; + +import org.bukkit.entity.Player; + +import java.util.function.Supplier; + +/** + * Tag to be replaced that does not depend on the player. + */ +public class SimpleTag implements Tag { + + private final String name; + private final Supplier replacementFunction; + + public SimpleTag(String name, Supplier replacementFunction) { + this.name = name; + this.replacementFunction = replacementFunction; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue(Player player) { + return replacementFunction.get(); + } +} diff --git a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java index 4489f7a7..5129777d 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java @@ -2,44 +2,15 @@ package fr.xephi.authme.util.lazytags; import org.bukkit.entity.Player; -import java.util.function.Function; -import java.util.function.Supplier; - /** - * Represents a tag in a text that can be replaced with data (which may depend on the Player). + * Represents a tag in a text to be replaced with a value (which may depend on the Player). */ -public class Tag { - - private final String name; - private final Function replacementFunction; +public interface Tag { /** - * Constructor. - * - * @param name the tag (placeholder) that will be replaced - * @param replacementFunction the function producing the replacement + * @return the tag to replace */ - public Tag(String name, Function replacementFunction) { - this.name = name; - this.replacementFunction = replacementFunction; - } - - /** - * Constructor. - * - * @param name the tag (placeholder) that will be replaced - * @param replacementFunction supplier providing the text to replace the tag with - */ - public Tag(String name, Supplier replacementFunction) { - this(name, p -> replacementFunction.get()); - } - - /** - * @return the tag - */ - public String getName() { - return name; - } + String getName(); /** * Returns the value to replace the tag with for the given player. @@ -47,7 +18,5 @@ public class Tag { * @param player the player to evaluate the replacement for * @return the replacement */ - public String getValue(Player player) { - return replacementFunction.apply(player); - } + String getValue(Player player); } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java new file mode 100644 index 00000000..dc8eaef9 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.util.lazytags; + +import org.bukkit.entity.Player; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Utility class for creating tags. + */ +public final class TagBuilder { + + private TagBuilder() { + } + + public static Tag createTag(String name, Function replacementFunction) { + return new PlayerTag(name, replacementFunction); + } + + public static Tag createTag(String name, Supplier replacementFunction) { + return new SimpleTag(name, replacementFunction); + } +} diff --git a/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java index 010761a0..37a3386a 100644 --- a/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java +++ b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java @@ -9,6 +9,7 @@ import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.GeoIpService; import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.entity.Player; import org.junit.Rule; import org.junit.Test; @@ -19,6 +20,7 @@ import org.mockito.Mock; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Arrays; import java.util.List; import static org.hamcrest.Matchers.contains; @@ -66,9 +68,8 @@ public class WelcomeMessageConfigurationTest { public void shouldLoadWelcomeMessage() throws IOException { // given String welcomeMessage = "This is my welcome message for testing\nBye!"; - Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + setWelcomeMessageAndReload(welcomeMessage); Player player = mock(Player.class); - welcomeMessageConfiguration.reload(); // when List result = welcomeMessageConfiguration.getWelcomeMessage(player); @@ -83,8 +84,7 @@ public class WelcomeMessageConfigurationTest { public void shouldReplaceNameAndIpAndCountry() throws IOException { // given String welcomeMessage = "Hello {PLAYER}, your IP is {IP}\nYour country is {COUNTRY}.\nWelcome to {SERVER}!"; - Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); - welcomeMessageConfiguration.reload(); + setWelcomeMessageAndReload(welcomeMessage); Player player = mock(Player.class); given(player.getName()).willReturn("Bobby"); @@ -103,4 +103,39 @@ public class WelcomeMessageConfigurationTest { verify(server, only()).getServerName(); verifyZeroInteractions(playerCache); } + + @Test + public void shouldApplyOtherReplacements() throws IOException { + // given + String welcomeMessage = "{ONLINE}/{MAXPLAYERS} online\n{LOGINS} logged in\nYour world is {WORLD}\nServer: {VERSION}"; + setWelcomeMessageAndReload(welcomeMessage); + given(bukkitService.getOnlinePlayers()).willReturn((List) Arrays.asList(mock(Player.class), mock(Player.class))); + given(server.getMaxPlayers()).willReturn(20); + given(playerCache.getLogged()).willReturn(1); + given(server.getBukkitVersion()).willReturn("Bukkit-456.77.8"); + + World world = mock(World.class); + given(world.getName()).willReturn("Hub"); + Player player = mock(Player.class); + given(player.getWorld()).willReturn(world); + + // when + List result = welcomeMessageConfiguration.getWelcomeMessage(player); + + // then + assertThat(result, hasSize(4)); + assertThat(result.get(0), equalTo("2/20 online")); + assertThat(result.get(1), equalTo("1 logged in")); + assertThat(result.get(2), equalTo("Your world is Hub")); + assertThat(result.get(3), equalTo("Server: Bukkit-456.77.8")); + } + + private void setWelcomeMessageAndReload(String welcomeMessage) { + try { + Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + } catch (IOException e) { + throw new IllegalStateException("Could not write to '" + welcomeFile + "'", e); + } + welcomeMessageConfiguration.reload(); + } } From 7578247085c58b4c2bc84df42b87ec81b7d7213c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 24 Jan 2017 21:39:01 +0100 Subject: [PATCH 05/79] Write tests for BukkitService --- .../authme/service/BukkitServiceTest.java | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java b/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java index 597f89a7..6f869564 100644 --- a/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/BukkitServiceTest.java @@ -9,10 +9,14 @@ import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.Collection; @@ -20,9 +24,13 @@ import java.util.Collection; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link BukkitService}. @@ -38,10 +46,13 @@ public class BukkitServiceTest { private Settings settings; @Mock private Server server; + @Mock + private BukkitScheduler scheduler; @Before public void constructBukkitService() { ReflectionTestUtils.setField(Bukkit.class, null, "server", server); + given(server.getScheduler()).willReturn(scheduler); given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(true); bukkitService = new BukkitService(authMe, settings); } @@ -101,6 +112,191 @@ public class BukkitServiceTest { verify(server).dispatchCommand(consoleSender, command); } + @Test + public void shouldScheduleSyncDelayedTask() { + // given + Runnable task = () -> {/* noop */}; + given(scheduler.scheduleSyncDelayedTask(authMe, task)).willReturn(123); + + // when + int taskId = bukkitService.scheduleSyncDelayedTask(task); + + // then + verify(scheduler, only()).scheduleSyncDelayedTask(authMe, task); + assertThat(taskId, equalTo(123)); + } + + @Test + public void shouldScheduleSyncDelayedTaskWithDelay() { + // given + Runnable task = () -> {/* noop */}; + int delay = 3; + given(scheduler.scheduleSyncDelayedTask(authMe, task, delay)).willReturn(44); + + // when + int taskId = bukkitService.scheduleSyncDelayedTask(task, delay); + + // then + verify(scheduler, only()).scheduleSyncDelayedTask(authMe, task, delay); + assertThat(taskId, equalTo(44)); + } + + @Test + public void shouldScheduleSyncTask() { + // given + BukkitService spy = Mockito.spy(bukkitService); + doReturn(1).when(spy).scheduleSyncDelayedTask(any(Runnable.class)); + Runnable task = mock(Runnable.class); + + // when + spy.scheduleSyncTaskFromOptionallyAsyncTask(task); + + // then + verify(spy).scheduleSyncDelayedTask(task); + verifyZeroInteractions(task); + } + + @Test + public void shouldRunTaskDirectly() { + // given + given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(false); + bukkitService.reload(settings); + BukkitService spy = Mockito.spy(bukkitService); + Runnable task = mock(Runnable.class); + + // when + spy.scheduleSyncTaskFromOptionallyAsyncTask(task); + + // then + verify(task).run(); + verify(spy, only()).scheduleSyncTaskFromOptionallyAsyncTask(task); + } + + @Test + public void shouldRunTask() { + // given + Runnable task = () -> {/* noop */}; + BukkitTask bukkitTask = mock(BukkitTask.class); + given(scheduler.runTask(authMe, task)).willReturn(bukkitTask); + + // when + BukkitTask resultingTask = bukkitService.runTask(task); + + // then + assertThat(resultingTask, equalTo(bukkitTask)); + verify(scheduler, only()).runTask(authMe, task); + } + + @Test + public void shouldRunTaskLater() { + // given + Runnable task = () -> {/* noop */}; + BukkitTask bukkitTask = mock(BukkitTask.class); + long delay = 400; + given(scheduler.runTaskLater(authMe, task, delay)).willReturn(bukkitTask); + + // when + BukkitTask resultingTask = bukkitService.runTaskLater(task, delay); + + // then + assertThat(resultingTask, equalTo(bukkitTask)); + verify(scheduler, only()).runTaskLater(authMe, task, delay); + } + + @Test + public void shouldRunTaskInAsync() { + // given + Runnable task = mock(Runnable.class); + BukkitService spy = Mockito.spy(bukkitService); + doReturn(null).when(spy).runTaskAsynchronously(task); + + // when + spy.runTaskOptionallyAsync(task); + + // then + verifyZeroInteractions(task); + verify(spy).runTaskAsynchronously(task); + } + + @Test + public void shouldRunTaskDirectlyIfConfigured() { + // given + given(settings.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(false); + bukkitService.reload(settings); + BukkitService spy = Mockito.spy(bukkitService); + Runnable task = mock(Runnable.class); + + // when + spy.runTaskOptionallyAsync(task); + + // then + verify(task).run(); + verify(spy, only()).runTaskOptionallyAsync(task); + } + + @Test + public void shouldRunTaskAsynchronously() { + // given + Runnable task = () -> {/* noop */}; + BukkitTask bukkitTask = mock(BukkitTask.class); + given(scheduler.runTaskAsynchronously(authMe, task)).willReturn(bukkitTask); + + // when + BukkitTask resultingTask = bukkitService.runTaskAsynchronously(task); + + // then + assertThat(resultingTask, equalTo(bukkitTask)); + verify(scheduler, only()).runTaskAsynchronously(authMe, task); + } + + @Test + public void shouldRunTaskTimerAsynchronously() { + // given + Runnable task = () -> {/* */}; + long delay = 20L; + long period = 4000L; + BukkitTask bukkitTask = mock(BukkitTask.class); + given(scheduler.runTaskTimerAsynchronously(authMe, task, delay, period)).willReturn(bukkitTask); + + // when + BukkitTask resultingTask = bukkitService.runTaskTimerAsynchronously(task, delay, period); + + // then + assertThat(resultingTask, equalTo(bukkitTask)); + verify(scheduler).runTaskTimerAsynchronously(authMe, task, delay, period); + } + + @Test + public void shouldRunTaskTimer() { + // given + BukkitRunnable bukkitRunnable = mock(BukkitRunnable.class); + long delay = 20; + long period = 80; + BukkitTask bukkitTask = mock(BukkitTask.class); + given(bukkitRunnable.runTaskTimer(authMe, delay, period)).willReturn(bukkitTask); + + // when + BukkitTask result = bukkitService.runTaskTimer(bukkitRunnable, delay, period); + + // then + assertThat(result, equalTo(bukkitTask)); + verify(bukkitRunnable).runTaskTimer(authMe, delay, period); + } + + @Test + public void shouldBroadcastMessage() { + // given + String message = "Important message to all"; + given(server.broadcastMessage(message)).willReturn(24); + + // when + int result = bukkitService.broadcastMessage(message); + + // then + assertThat(result, equalTo(24)); + verify(server).broadcastMessage(message); + } + // Note: This method is used through reflections public static Player[] onlinePlayersImpl() { return new Player[]{ From 89c70ff447fb0a08290221418bc7a0f76afc1eb8 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 Jan 2017 13:54:37 +0100 Subject: [PATCH 06/79] #1026 Add more tags for forced commands (lazily replaced) (#214) * #1026 Add more tags for forced commands (lazily replaced) - Extract lazy replacement of tags to its own class - Implement wrapper to replace a String property within an object - Use wrapper in command manager and add new tags * Make argument type generic in lazy tags util --- .../settings/WelcomeMessageConfiguration.java | 59 +----- .../commandconfig/CommandManager.java | 43 +++- .../{PlayerTag.java => DependentTag.java} | 16 +- .../xephi/authme/util/lazytags/SimpleTag.java | 10 +- .../fr/xephi/authme/util/lazytags/Tag.java | 14 +- .../authme/util/lazytags/TagBuilder.java | 10 +- .../authme/util/lazytags/TagReplacer.java | 102 ++++++++++ .../util/lazytags/WrappedTagReplacer.java | 61 ++++++ .../commandconfig/CommandManagerTest.java | 188 +++++++++--------- .../filegeneration/GenerateCommandsYml.java | 9 +- .../commandconfig/commands.complete.yml | 4 +- .../commandconfig/commands.incomplete.yml | 2 +- 12 files changed, 329 insertions(+), 189 deletions(-) rename src/main/java/fr/xephi/authme/util/lazytags/{PlayerTag.java => DependentTag.java} (56%) create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java diff --git a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java index 8fcb2163..60666f7a 100644 --- a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java +++ b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java @@ -8,6 +8,7 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.lazytags.Tag; +import fr.xephi.authme.util.lazytags.TagReplacer; import org.bukkit.Server; import org.bukkit.entity.Player; @@ -19,9 +20,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; import static fr.xephi.authme.util.FileUtils.copyFileFromResource; import static fr.xephi.authme.util.lazytags.TagBuilder.createTag; @@ -48,7 +47,7 @@ public class WelcomeMessageConfiguration implements Reloadable { private PlayerCache playerCache; /** List of all supported tags for the welcome message. */ - private final List availableTags = Arrays.asList( + private final List> availableTags = Arrays.asList( createTag("&", () -> "\u00a7"), createTag("{PLAYER}", pl -> pl.getName()), createTag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())), @@ -60,16 +59,13 @@ public class WelcomeMessageConfiguration implements Reloadable { createTag("{VERSION}", () -> server.getBukkitVersion()), createTag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); - /** Welcome message, by lines. */ - private List welcomeMessage; - /** Tags used in the welcome message. */ - private List usedTags; + private TagReplacer messageSupplier; @PostConstruct @Override public void reload() { - welcomeMessage = readWelcomeFile(); - usedTags = determineUsedTags(welcomeMessage); + List welcomeMessage = readWelcomeFile(); + messageSupplier = TagReplacer.newReplacer(availableTags, welcomeMessage); } /** @@ -79,22 +75,7 @@ public class WelcomeMessageConfiguration implements Reloadable { * @return the welcome message */ public List getWelcomeMessage(Player player) { - // Note ljacqu 20170121: Using a Map might seem more natural here but we avoid doing so for performance - // Although the performance gain here is probably minimal... - List tagValues = new LinkedList<>(); - for (Tag tag : usedTags) { - tagValues.add(new TagValue(tag.getName(), tag.getValue(player))); - } - - List adaptedMessages = new LinkedList<>(); - for (String line : welcomeMessage) { - String adaptedLine = line; - for (TagValue tagValue : tagValues) { - adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value); - } - adaptedMessages.add(adaptedLine); - } - return adaptedMessages; + return messageSupplier.getAdaptedMessages(player); } /** @@ -113,32 +94,4 @@ public class WelcomeMessageConfiguration implements Reloadable { } return Collections.emptyList(); } - - /** - * Determines which tags are used in the message. - * - * @param welcomeMessage the lines of the welcome message - * @return the tags - */ - private List determineUsedTags(List welcomeMessage) { - return availableTags.stream() - .filter(tag -> welcomeMessage.stream().anyMatch(msg -> msg.contains(tag.getName()))) - .collect(Collectors.toList()); - } - - private static final class TagValue { - - private final String tag; - private final String value; - - TagValue(String tag, String value) { - this.tag = tag; - this.value = value; - } - - @Override - public String toString() { - return "TagValue[tag='" + tag + "', value='" + value + "']"; - } - } } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java index bc0abf38..d87ebd5e 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -5,13 +5,21 @@ import ch.jalu.configme.resource.YamlFileResource; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import fr.xephi.authme.util.lazytags.Tag; +import fr.xephi.authme.util.lazytags.WrappedTagReplacer; import org.bukkit.entity.Player; import javax.inject.Inject; import java.io.File; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import static fr.xephi.authme.util.lazytags.TagBuilder.createTag; + /** * Manages configurable commands to be run when various events occur. */ @@ -19,15 +27,20 @@ public class CommandManager implements Reloadable { private final File dataFolder; private final BukkitService bukkitService; + private final GeoIpService geoIpService; private final CommandMigrationService commandMigrationService; + private final List> availableTags = buildAvailableTags(); - private CommandConfig commandConfig; + private WrappedTagReplacer onJoinCommands; + private WrappedTagReplacer onLoginCommands; + private WrappedTagReplacer onRegisterCommands; @Inject - CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, + CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, GeoIpService geoIpService, CommandMigrationService commandMigrationService) { this.dataFolder = dataFolder; this.bukkitService = bukkitService; + this.geoIpService = geoIpService; this.commandMigrationService = commandMigrationService; reload(); } @@ -38,7 +51,7 @@ public class CommandManager implements Reloadable { * @param player the joining player */ public void runCommandsOnJoin(Player player) { - executeCommands(player, commandConfig.getOnJoin()); + executeCommands(player, onJoinCommands.getAdaptedItems(player)); } /** @@ -47,7 +60,7 @@ public class CommandManager implements Reloadable { * @param player the player who has registered */ public void runCommandsOnRegister(Player player) { - executeCommands(player, commandConfig.getOnRegister()); + executeCommands(player, onRegisterCommands.getAdaptedItems(player)); } /** @@ -56,11 +69,11 @@ public class CommandManager implements Reloadable { * @param player the player that logged in */ public void runCommandsOnLogin(Player player) { - executeCommands(player, commandConfig.getOnLogin()); + executeCommands(player, onLoginCommands.getAdaptedItems(player)); } - private void executeCommands(Player player, Map commands) { - for (Command command : commands.values()) { + private void executeCommands(Player player, List commands) { + for (Command command : commands) { final String execution = command.getCommand().replace("%p", player.getName()); if (Executor.CONSOLE.equals(command.getExecutor())) { bukkitService.dispatchConsoleCommand(execution); @@ -77,8 +90,22 @@ public class CommandManager implements Reloadable { SettingsManager settingsManager = new SettingsManager( new YamlFileResource(file), commandMigrationService, CommandSettingsHolder.class); - commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); + onJoinCommands = newReplacer(commandConfig.getOnJoin()); + onLoginCommands = newReplacer(commandConfig.getOnLogin()); + onRegisterCommands = newReplacer(commandConfig.getOnRegister()); } + private WrappedTagReplacer newReplacer(Map commands) { + return new WrappedTagReplacer<>(availableTags, commands.values(), Command::getCommand, + (cmd, text) -> new Command(text, cmd.getExecutor())); + } + private List> buildAvailableTags() { + return Arrays.asList( + createTag("%p", pl -> pl.getName()), + createTag("%nick", pl -> pl.getDisplayName()), + createTag("%ip", pl -> PlayerUtils.getPlayerIp(pl)), + createTag("%country", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); + } } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java b/src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java similarity index 56% rename from src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java rename to src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java index 3c476237..5fb0cd3d 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/PlayerTag.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java @@ -1,16 +1,16 @@ package fr.xephi.authme.util.lazytags; -import org.bukkit.entity.Player; - import java.util.function.Function; /** - * Replaceable tag whose value depends on the player. + * Replaceable tag whose value depends on an argument. + * + * @param the argument type */ -public class PlayerTag implements Tag { +public class DependentTag implements Tag { private final String name; - private final Function replacementFunction; + private final Function replacementFunction; /** * Constructor. @@ -18,7 +18,7 @@ public class PlayerTag implements Tag { * @param name the tag (placeholder) that will be replaced * @param replacementFunction the function producing the replacement */ - public PlayerTag(String name, Function replacementFunction) { + public DependentTag(String name, Function replacementFunction) { this.name = name; this.replacementFunction = replacementFunction; } @@ -29,7 +29,7 @@ public class PlayerTag implements Tag { } @Override - public String getValue(Player player) { - return replacementFunction.apply(player); + public String getValue(A argument) { + return replacementFunction.apply(argument); } } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java index 08ece1c3..a5bb58a2 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java @@ -1,13 +1,13 @@ package fr.xephi.authme.util.lazytags; -import org.bukkit.entity.Player; - import java.util.function.Supplier; /** - * Tag to be replaced that does not depend on the player. + * Tag to be replaced that does not depend on an argument. + * + * @param type of the argument (not used in this implementation) */ -public class SimpleTag implements Tag { +public class SimpleTag implements Tag { private final String name; private final Supplier replacementFunction; @@ -23,7 +23,7 @@ public class SimpleTag implements Tag { } @Override - public String getValue(Player player) { + public String getValue(A argument) { return replacementFunction.get(); } } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java index 5129777d..2c7c6ba5 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java @@ -1,11 +1,11 @@ package fr.xephi.authme.util.lazytags; -import org.bukkit.entity.Player; - /** - * Represents a tag in a text to be replaced with a value (which may depend on the Player). + * Represents a tag in a text to be replaced with a value (which may depend on some argument). + * + * @param argument type the replacement may depend on */ -public interface Tag { +public interface Tag { /** * @return the tag to replace @@ -13,10 +13,10 @@ public interface Tag { String getName(); /** - * Returns the value to replace the tag with for the given player. + * Returns the value to replace the tag with for the given argument. * - * @param player the player to evaluate the replacement for + * @param argument the argument to evaluate the replacement for * @return the replacement */ - String getValue(Player player); + String getValue(A argument); } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java index dc8eaef9..677b30e2 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java @@ -1,7 +1,5 @@ package fr.xephi.authme.util.lazytags; -import org.bukkit.entity.Player; - import java.util.function.Function; import java.util.function.Supplier; @@ -13,11 +11,11 @@ public final class TagBuilder { private TagBuilder() { } - public static Tag createTag(String name, Function replacementFunction) { - return new PlayerTag(name, replacementFunction); + public static Tag createTag(String name, Function replacementFunction) { + return new DependentTag<>(name, replacementFunction); } - public static Tag createTag(String name, Supplier replacementFunction) { - return new SimpleTag(name, replacementFunction); + public static Tag createTag(String name, Supplier replacementFunction) { + return new SimpleTag<>(name, replacementFunction); } } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java new file mode 100644 index 00000000..660132fb --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java @@ -0,0 +1,102 @@ +package fr.xephi.authme.util.lazytags; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Replaces tags lazily by first determining which tags are being used + * and only applying those replacements afterwards. + * + * @param the argument type + */ +public class TagReplacer { + + private final List> tags; + private final Collection messages; + + /** + * Private constructor. Use {@link #newReplacer(Collection, Collection)}. + * + * @param tags the tags that are being used in the messages + * @param messages the messages + */ + private TagReplacer(List> tags, Collection messages) { + this.tags = tags; + this.messages = messages; + } + + /** + * Creates a new instance of this class, which will provide the given + * messages adapted with the provided tags. + * + * @param allTags all available tags + * @param messages the messages to use + * @param the argument type + * @return new tag replacer instance + */ + public static TagReplacer newReplacer(Collection> allTags, Collection messages) { + List> usedTags = determineUsedTags(allTags, messages); + return new TagReplacer<>(usedTags, messages); + } + + /** + * Returns the messages with the tags applied for the given argument. + * + * @param argument the argument to get the messages for + * @return the adapted messages + */ + public List getAdaptedMessages(A argument) { + // Note ljacqu 20170121: Using a Map might seem more natural here but we avoid doing so for performance + // Although the performance gain here is probably minimal... + List tagValues = new LinkedList<>(); + for (Tag tag : tags) { + tagValues.add(new TagValue(tag.getName(), tag.getValue(argument))); + } + + List adaptedMessages = new LinkedList<>(); + for (String line : messages) { + String adaptedLine = line; + for (TagValue tagValue : tagValues) { + adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value); + } + adaptedMessages.add(adaptedLine); + } + return adaptedMessages; + } + + /** + * Determines which tags are used somewhere in the given list of messages. + * + * @param allTags all available tags + * @param messages the messages + * @param argument type + * @return tags used at least once + */ + private static List> determineUsedTags(Collection> allTags, Collection messages) { + return allTags.stream() + .filter(tag -> messages.stream().anyMatch(msg -> msg.contains(tag.getName()))) + .collect(Collectors.toList()); + } + + /** (Tag, value) pair. */ + private static final class TagValue { + + /** The tag to replace. */ + private final String tag; + /** The value to replace with. */ + private final String value; + + TagValue(String tag, String value) { + this.tag = tag; + this.value = value; + } + + @Override + public String toString() { + return "TagValue[tag='" + tag + "', value='" + value + "']"; + } + } + +} diff --git a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java new file mode 100644 index 00000000..92c3f70d --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java @@ -0,0 +1,61 @@ +package fr.xephi.authme.util.lazytags; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Applies tags lazily to the String property of an item. This class wraps + * a {@link TagReplacer} with the extraction of the String property and + * the creation of new items with the adapted string property. + * + * @param the item type + * @param the argument type to evaluate the replacements + */ +public class WrappedTagReplacer { + + private final Collection items; + private final BiFunction itemCreator; + private final TagReplacer tagReplacer; + + /** + * Constructor. + * + * @param allTags all available tags + * @param items the items to apply the replacements on + * @param stringGetter getter of the String property to adapt on the items + * @param itemCreator a function of signature (T, String) -> T: the original item and the adapted String are passed + */ + public WrappedTagReplacer(Collection> allTags, + Collection items, + Function stringGetter, + BiFunction itemCreator) { + this.items = items; + this.itemCreator = itemCreator; + + List stringItems = items.stream().map(stringGetter).collect(Collectors.toList()); + tagReplacer = TagReplacer.newReplacer(allTags, stringItems); + } + + /** + * Creates adapted items for the given argument. + * + * @param argument the argument to adapt the items for + * @return the adapted items + */ + public List getAdaptedItems(A argument) { + List adaptedStrings = tagReplacer.getAdaptedMessages(argument); + List adaptedItems = new LinkedList<>(); + + Iterator originalItemsIter = items.iterator(); + Iterator newStringsIter = adaptedStrings.iterator(); + while (originalItemsIter.hasNext() && newStringsIter.hasNext()) { + adaptedItems.add(itemCreator.apply(originalItemsIter.next(), newStringsIter.next())); + } + return adaptedItems; + } +} diff --git a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java index fb8e4e40..3ec3b447 100644 --- a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java +++ b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java @@ -1,9 +1,9 @@ package fr.xephi.authme.settings.commandconfig; import com.google.common.io.Files; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.settings.SettingsMigrationService; import org.bukkit.entity.Player; import org.junit.Before; @@ -17,13 +17,7 @@ import org.mockito.junit.MockitoJUnitRunner; import java.io.File; import java.io.IOException; -import java.util.function.BiConsumer; -import static fr.xephi.authme.settings.commandconfig.CommandConfigTestHelper.isCommand; -import static java.lang.String.format; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -31,6 +25,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link CommandManager}. @@ -41,104 +36,116 @@ public class CommandManagerTest { private static final String TEST_FILES_FOLDER = "/fr/xephi/authme/settings/commandconfig/"; private CommandManager manager; + private Player player; + @InjectMocks private CommandMigrationService commandMigrationService; - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Mock private BukkitService bukkitService; @Mock + private GeoIpService geoIpService; + @Mock private SettingsMigrationService settingsMigrationService; + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private File testFolder; @Before public void setup() throws IOException { testFolder = temporaryFolder.newFolder(); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldLoadCompleteFile() { - // given - copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml"); - - // when - initManager(); - - // then - CommandConfig commandConfig = ReflectionTestUtils.getFieldValue(CommandManager.class, manager, "commandConfig"); - assertThat(commandConfig.getOnJoin().keySet(), contains("broadcast")); - assertThat(commandConfig.getOnJoin().values(), contains(isCommand("broadcast %p has joined", Executor.CONSOLE))); - assertThat(commandConfig.getOnRegister().keySet(), contains("announce", "notify")); - assertThat(commandConfig.getOnRegister().values(), contains( - isCommand("me I just registered", Executor.PLAYER), - isCommand("log %p registered", Executor.CONSOLE))); - assertThat(commandConfig.getOnLogin().keySet(), contains("welcome", "show_motd", "display_list")); - assertThat(commandConfig.getOnLogin().values(), contains( - isCommand("msg %p Welcome back", Executor.CONSOLE), - isCommand("motd", Executor.PLAYER), - isCommand("list", Executor.PLAYER))); - } - - @Test - public void shouldLoadIncompleteFile() { - // given - copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.incomplete.yml"); - - // when - initManager(); - - // then - CommandConfig commandConfig = ReflectionTestUtils.getFieldValue(CommandManager.class, manager, "commandConfig"); - assertThat(commandConfig.getOnJoin().values(), contains(isCommand("broadcast %p has joined", Executor.CONSOLE))); - assertThat(commandConfig.getOnLogin().values(), contains( - isCommand("msg %p Welcome back", Executor.CONSOLE), - isCommand("list", Executor.PLAYER))); - assertThat(commandConfig.getOnRegister(), anEmptyMap()); - } - - @Test - public void shouldExecuteCommandsOnJoin() { - // given - String name = "Bobby1"; - - // when - testCommandExecution(name, CommandManager::runCommandsOnJoin); - - // then - verify(bukkitService, only()).dispatchConsoleCommand(format("broadcast %s has joined", name)); - } - - @Test - public void shouldExecuteCommandsOnRegister() { - // given - String name = "luis"; - - // when - testCommandExecution(name, CommandManager::runCommandsOnRegister); - - // then - verify(bukkitService).dispatchCommand(any(Player.class), eq("me I just registered")); - verify(bukkitService).dispatchConsoleCommand(format("log %s registered", name)); - verifyNoMoreInteractions(bukkitService); + player = mockPlayer(); } @Test public void shouldExecuteCommandsOnLogin() { // given - String name = "plaYer01"; + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml"); + initManager(); // when - testCommandExecution(name, CommandManager::runCommandsOnLogin); + manager.runCommandsOnLogin(player); // then - verify(bukkitService).dispatchConsoleCommand(format("msg %s Welcome back", name)); + verify(bukkitService).dispatchConsoleCommand("msg Bobby Welcome back"); verify(bukkitService).dispatchCommand(any(Player.class), eq("motd")); verify(bukkitService).dispatchCommand(any(Player.class), eq("list")); verifyNoMoreInteractions(bukkitService); + verifyZeroInteractions(geoIpService); + } + + @Test + public void shouldExecuteCommandsOnLoginWithIncompleteConfig() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.incomplete.yml"); + initManager(); + + // when + manager.runCommandsOnLogin(player); + + // then + verify(bukkitService).dispatchConsoleCommand("msg Bobby Welcome back, bob"); + verify(bukkitService).dispatchCommand(any(Player.class), eq("list")); + verifyNoMoreInteractions(bukkitService); + verifyZeroInteractions(geoIpService); + } + + @Test + public void shouldExecuteCommandsOnJoin() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml"); + initManager(); + + // when + manager.runCommandsOnJoin(player); + + // then + verify(bukkitService, only()).dispatchConsoleCommand("broadcast bob has joined"); + verifyZeroInteractions(geoIpService); + } + + @Test + public void shouldExecuteCommandsOnJoinWithIncompleteConfig() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.incomplete.yml"); + initManager(); + + // when + manager.runCommandsOnJoin(player); + + // then + verify(bukkitService, only()).dispatchConsoleCommand("broadcast Bobby has joined"); + verifyZeroInteractions(geoIpService); + } + + @Test + public void shouldExecuteCommandsOnRegister() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml"); + initManager(); + + // when + manager.runCommandsOnRegister(player); + + // then + verify(bukkitService).dispatchCommand(any(Player.class), eq("me I just registered")); + verify(bukkitService).dispatchConsoleCommand("log Bobby (127.0.0.3, Syldavia) registered"); + verifyNoMoreInteractions(bukkitService); + } + + @Test + public void shouldExecuteCommandsOnRegisterWithIncompleteConfig() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.incomplete.yml"); + initManager(); + + // when + manager.runCommandsOnRegister(player); + + // then + verifyZeroInteractions(bukkitService, geoIpService); } @Test @@ -147,18 +154,8 @@ public class CommandManagerTest { TestHelper.validateHasOnlyPrivateEmptyConstructor(CommandSettingsHolder.class); } - - private void testCommandExecution(String playerName, BiConsumer testMethod) { - copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml"); - initManager(); - Player player = mock(Player.class); - given(player.getName()).willReturn(playerName); - - testMethod.accept(manager, player); - } - private void initManager() { - manager = new CommandManager(testFolder, bukkitService, commandMigrationService); + manager = new CommandManager(testFolder, bukkitService, geoIpService, commandMigrationService); } private void copyJarFileAsCommandsYml(String path) { @@ -171,4 +168,13 @@ public class CommandManagerTest { } } + private Player mockPlayer() { + Player player = mock(Player.class); + given(player.getName()).willReturn("Bobby"); + given(player.getDisplayName()).willReturn("bob"); + String ip = "127.0.0.3"; + TestHelper.mockPlayerIp(player, ip); + given(geoIpService.getCountryName(ip)).willReturn("Syldavia"); + return player; + } } diff --git a/src/test/java/tools/filegeneration/GenerateCommandsYml.java b/src/test/java/tools/filegeneration/GenerateCommandsYml.java index 73113ccc..b99eef52 100644 --- a/src/test/java/tools/filegeneration/GenerateCommandsYml.java +++ b/src/test/java/tools/filegeneration/GenerateCommandsYml.java @@ -26,7 +26,7 @@ public class GenerateCommandsYml implements AutoToolTask { // Get default and add sample entry CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.getDefaultValue(); commandConfig.setOnLogin( - ImmutableMap.of("welcome", newCommand("msg %p Welcome back!", Executor.PLAYER))); + ImmutableMap.of("welcome", new Command("msg %p Welcome back!", Executor.PLAYER))); // Export the value to the file SettingsManager settingsManager = new SettingsManager( @@ -41,11 +41,4 @@ public class GenerateCommandsYml implements AutoToolTask { public String getTaskName() { return "generateCommandsYml"; } - - private static Command newCommand(String commandLine, Executor executor) { - Command command = new Command(); - command.setCommand(commandLine); - command.setExecutor(executor); - return command; - } } diff --git a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml index 8c6bf79d..757f09ca 100644 --- a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml +++ b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml @@ -2,14 +2,14 @@ onJoin: broadcast: - command: 'broadcast %p has joined' + command: 'broadcast %nick has joined' executor: CONSOLE onRegister: announce: command: 'me I just registered' executor: PLAYER notify: - command: 'log %p registered' + command: 'log %p (%ip, %country) registered' executor: CONSOLE onLogin: welcome: diff --git a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml index 2eef86b0..a1e9060f 100644 --- a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml +++ b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml @@ -6,7 +6,7 @@ onJoin: executor: CONSOLE onLogin: welcome: - command: 'msg %p Welcome back' + command: 'msg %p Welcome back, %nick' executor: CONSOLE show_motd: # command: 'motd' <-- mandatory property, so entry should be ignored From 6569c275eb0582722395f81efebac043924218fc Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Sun, 29 Jan 2017 15:48:07 +0100 Subject: [PATCH 07/79] Replace mcstats with bStats (#215) --- pom.xml | 33 - .../authme/initialization/OnStartupTasks.java | 43 +- .../java/fr/xephi/authme/metrics/Metrics.java | 1031 +++++++++++++++++ 3 files changed, 1046 insertions(+), 61 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/metrics/Metrics.java diff --git a/pom.xml b/pom.xml index 37e5f505..622af6bd 100644 --- a/pom.xml +++ b/pom.xml @@ -108,10 +108,6 @@ junit junit - - json-simple - com.googlecode.json-simple - persistence-api javax.persistence @@ -146,10 +142,6 @@ junit junit - - json-simple - com.googlecode.json-simple - persistence-api javax.persistence @@ -276,11 +268,6 @@ javax.inject fr.xephi.authme.libs.javax.inject - - - org.mcstats - fr.xephi.authme - target/${project.finalName}-spigot.jar @@ -331,11 +318,6 @@ javax.inject fr.xephi.authme.libs.javax.inject - - - org.mcstats - fr.xephi.authme - target/${project.finalName}-legacy.jar @@ -544,21 +526,6 @@ - - - org.mcstats.bukkit - metrics - R8-SNAPSHOT - compile - - - org.bukkit - bukkit - - - true - - com.comphenix.protocol diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 0dd42181..e939d11f 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -6,6 +6,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.metrics.Metrics; import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.service.BukkitService; @@ -18,10 +19,8 @@ import fr.xephi.authme.util.StringUtils; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.mcstats.Metrics; import javax.inject.Inject; -import java.io.IOException; import java.util.logging.Logger; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; @@ -45,40 +44,28 @@ public class OnStartupTasks { } public static void sendMetrics(AuthMe plugin, Settings settings) { - try { - final Metrics metrics = new Metrics(plugin); + final Metrics metrics = new Metrics(plugin); - final Metrics.Graph languageGraph = metrics.createGraph("Messages Language"); - final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); - languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) { - @Override - public int getValue() { - return 1; - } - }); + metrics.addCustomChart(new Metrics.SimplePie("messages_language") { + @Override + public String getValue() { + return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); + } + }); - final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend"); - final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString(); - databaseBackend.addPlotter(new Metrics.Plotter(dataSource) { - @Override - public int getValue() { - return 1; - } - }); - - // Submit metrics - metrics.start(); - } catch (IOException e) { - // Failed to submit the metrics data - ConsoleLogger.logException("Can't send Metrics data! The plugin will work anyway...", e); - } + metrics.addCustomChart(new Metrics.SimplePie("database_backend") { + @Override + public String getValue() { + return settings.getProperty(DatabaseSettings.BACKEND).toString(); + } + }); } /** * Sets up the console filter if enabled. * * @param settings the settings - * @param logger the plugin logger + * @param logger the plugin logger */ public static void setupConsoleFilter(Settings settings, Logger logger) { if (!settings.getProperty(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE)) { diff --git a/src/main/java/fr/xephi/authme/metrics/Metrics.java b/src/main/java/fr/xephi/authme/metrics/Metrics.java new file mode 100644 index 00000000..714036ce --- /dev/null +++ b/src/main/java/fr/xephi/authme/metrics/Metrics.java @@ -0,0 +1,1031 @@ +package fr.xephi.authme.metrics; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +public class Metrics { + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData"; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final JavaPlugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(JavaPlugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + + // Load the data + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + if (config.getBoolean("enabled", true)) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { + } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, new Runnable() { + @Override + public void run() { + submitData(); + } + }); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount = Bukkit.getOnlinePlayers().size(); + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = org.bukkit.Bukkit.getVersion(); + bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + } catch (NoSuchFieldException ignored) { + continue; // Continue "searching" + } + // Found one! + try { + pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service))); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { + } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + + } + + /** + * Sends the data to the bStats server. + * + * @param data The data to send. + * + * @throws Exception If the request failed. + */ + private static void sendData(JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + connection.getInputStream().close(); // We don't care about the response - Just send our data :) + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * + * @return The gzipped String. + * + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes("UTF-8")); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + protected final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + protected JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData(); + + } + + /** + * Represents a custom simple pie. + */ + public static abstract class SimplePie extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimplePie(String chartId) { + super(chartId); + } + + /** + * Gets the value of the pie. + * + * @return The value of the pie. + */ + public abstract String getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + String value = getValue(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static abstract class AdvancedPie extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedPie(String chartId) { + super(chartId); + } + + /** + * Gets the values of the pie. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The values of the pie. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static abstract class SingleLineChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SingleLineChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @return The value of the chart. + */ + public abstract int getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + int value = getValue(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static abstract class MultiLineChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public MultiLineChart(String chartId) { + super(chartId); + } + + /** + * Gets the values of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The values of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static abstract class SimpleBarChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimpleBarChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static abstract class AdvancedBarChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedBarChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple map chart. + */ + public static abstract class SimpleMapChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public SimpleMapChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @return The value of the chart. + */ + public abstract Country getValue(); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + Country value = getValue(); + + if (value == null) { + // Null = skip the chart + return null; + } + data.put("value", value.getCountryIsoTag()); + return data; + } + + } + + /** + * Represents a custom advanced map chart. + */ + public static abstract class AdvancedMapChart extends CustomChart { + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + public AdvancedMapChart(String chartId) { + super(chartId); + } + + /** + * Gets the value of the chart. + * + * @param valueMap Just an empty map. The only reason it exists is to make your life easier. + * You don't have to create a map yourself! + * + * @return The value of the chart. + */ + public abstract HashMap getValues(HashMap valueMap); + + @Override + protected JSONObject getChartData() { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + HashMap map = getValues(new HashMap()); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey().getCountryIsoTag(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * A enum which is used for custom maps. + */ + public enum Country { + + /** + * bStats will use the country of the server. + */ + AUTO_DETECT("AUTO", "Auto Detected"), + + ANDORRA("AD", "Andorra"), + UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"), + AFGHANISTAN("AF", "Afghanistan"), + ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"), + ANGUILLA("AI", "Anguilla"), + ALBANIA("AL", "Albania"), + ARMENIA("AM", "Armenia"), + NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"), + ANGOLA("AO", "Angola"), + ANTARCTICA("AQ", "Antarctica"), + ARGENTINA("AR", "Argentina"), + AMERICAN_SAMOA("AS", "American Samoa"), + AUSTRIA("AT", "Austria"), + AUSTRALIA("AU", "Australia"), + ARUBA("AW", "Aruba"), + ÅLAND_ISLANDS("AX", "Åland Islands"), + AZERBAIJAN("AZ", "Azerbaijan"), + BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"), + BARBADOS("BB", "Barbados"), + BANGLADESH("BD", "Bangladesh"), + BELGIUM("BE", "Belgium"), + BURKINA_FASO("BF", "Burkina Faso"), + BULGARIA("BG", "Bulgaria"), + BAHRAIN("BH", "Bahrain"), + BURUNDI("BI", "Burundi"), + BENIN("BJ", "Benin"), + SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"), + BERMUDA("BM", "Bermuda"), + BRUNEI("BN", "Brunei"), + BOLIVIA("BO", "Bolivia"), + BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"), + BRAZIL("BR", "Brazil"), + BAHAMAS("BS", "Bahamas"), + BHUTAN("BT", "Bhutan"), + BOUVET_ISLAND("BV", "Bouvet Island"), + BOTSWANA("BW", "Botswana"), + BELARUS("BY", "Belarus"), + BELIZE("BZ", "Belize"), + CANADA("CA", "Canada"), + COCOS_ISLANDS("CC", "Cocos Islands"), + THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"), + CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"), + CONGO("CG", "Congo"), + SWITZERLAND("CH", "Switzerland"), + CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"), + COOK_ISLANDS("CK", "Cook Islands"), + CHILE("CL", "Chile"), + CAMEROON("CM", "Cameroon"), + CHINA("CN", "China"), + COLOMBIA("CO", "Colombia"), + COSTA_RICA("CR", "Costa Rica"), + CUBA("CU", "Cuba"), + CAPE_VERDE("CV", "Cape Verde"), + CURAÇAO("CW", "Curaçao"), + CHRISTMAS_ISLAND("CX", "Christmas Island"), + CYPRUS("CY", "Cyprus"), + CZECH_REPUBLIC("CZ", "Czech Republic"), + GERMANY("DE", "Germany"), + DJIBOUTI("DJ", "Djibouti"), + DENMARK("DK", "Denmark"), + DOMINICA("DM", "Dominica"), + DOMINICAN_REPUBLIC("DO", "Dominican Republic"), + ALGERIA("DZ", "Algeria"), + ECUADOR("EC", "Ecuador"), + ESTONIA("EE", "Estonia"), + EGYPT("EG", "Egypt"), + WESTERN_SAHARA("EH", "Western Sahara"), + ERITREA("ER", "Eritrea"), + SPAIN("ES", "Spain"), + ETHIOPIA("ET", "Ethiopia"), + FINLAND("FI", "Finland"), + FIJI("FJ", "Fiji"), + FALKLAND_ISLANDS("FK", "Falkland Islands"), + MICRONESIA("FM", "Micronesia"), + FAROE_ISLANDS("FO", "Faroe Islands"), + FRANCE("FR", "France"), + GABON("GA", "Gabon"), + UNITED_KINGDOM("GB", "United Kingdom"), + GRENADA("GD", "Grenada"), + GEORGIA("GE", "Georgia"), + FRENCH_GUIANA("GF", "French Guiana"), + GUERNSEY("GG", "Guernsey"), + GHANA("GH", "Ghana"), + GIBRALTAR("GI", "Gibraltar"), + GREENLAND("GL", "Greenland"), + GAMBIA("GM", "Gambia"), + GUINEA("GN", "Guinea"), + GUADELOUPE("GP", "Guadeloupe"), + EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"), + GREECE("GR", "Greece"), + SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"), + GUATEMALA("GT", "Guatemala"), + GUAM("GU", "Guam"), + GUINEA_BISSAU("GW", "Guinea-Bissau"), + GUYANA("GY", "Guyana"), + HONG_KONG("HK", "Hong Kong"), + HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"), + HONDURAS("HN", "Honduras"), + CROATIA("HR", "Croatia"), + HAITI("HT", "Haiti"), + HUNGARY("HU", "Hungary"), + INDONESIA("ID", "Indonesia"), + IRELAND("IE", "Ireland"), + ISRAEL("IL", "Israel"), + ISLE_OF_MAN("IM", "Isle Of Man"), + INDIA("IN", "India"), + BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"), + IRAQ("IQ", "Iraq"), + IRAN("IR", "Iran"), + ICELAND("IS", "Iceland"), + ITALY("IT", "Italy"), + JERSEY("JE", "Jersey"), + JAMAICA("JM", "Jamaica"), + JORDAN("JO", "Jordan"), + JAPAN("JP", "Japan"), + KENYA("KE", "Kenya"), + KYRGYZSTAN("KG", "Kyrgyzstan"), + CAMBODIA("KH", "Cambodia"), + KIRIBATI("KI", "Kiribati"), + COMOROS("KM", "Comoros"), + SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"), + NORTH_KOREA("KP", "North Korea"), + SOUTH_KOREA("KR", "South Korea"), + KUWAIT("KW", "Kuwait"), + CAYMAN_ISLANDS("KY", "Cayman Islands"), + KAZAKHSTAN("KZ", "Kazakhstan"), + LAOS("LA", "Laos"), + LEBANON("LB", "Lebanon"), + SAINT_LUCIA("LC", "Saint Lucia"), + LIECHTENSTEIN("LI", "Liechtenstein"), + SRI_LANKA("LK", "Sri Lanka"), + LIBERIA("LR", "Liberia"), + LESOTHO("LS", "Lesotho"), + LITHUANIA("LT", "Lithuania"), + LUXEMBOURG("LU", "Luxembourg"), + LATVIA("LV", "Latvia"), + LIBYA("LY", "Libya"), + MOROCCO("MA", "Morocco"), + MONACO("MC", "Monaco"), + MOLDOVA("MD", "Moldova"), + MONTENEGRO("ME", "Montenegro"), + SAINT_MARTIN("MF", "Saint Martin"), + MADAGASCAR("MG", "Madagascar"), + MARSHALL_ISLANDS("MH", "Marshall Islands"), + MACEDONIA("MK", "Macedonia"), + MALI("ML", "Mali"), + MYANMAR("MM", "Myanmar"), + MONGOLIA("MN", "Mongolia"), + MACAO("MO", "Macao"), + NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"), + MARTINIQUE("MQ", "Martinique"), + MAURITANIA("MR", "Mauritania"), + MONTSERRAT("MS", "Montserrat"), + MALTA("MT", "Malta"), + MAURITIUS("MU", "Mauritius"), + MALDIVES("MV", "Maldives"), + MALAWI("MW", "Malawi"), + MEXICO("MX", "Mexico"), + MALAYSIA("MY", "Malaysia"), + MOZAMBIQUE("MZ", "Mozambique"), + NAMIBIA("NA", "Namibia"), + NEW_CALEDONIA("NC", "New Caledonia"), + NIGER("NE", "Niger"), + NORFOLK_ISLAND("NF", "Norfolk Island"), + NIGERIA("NG", "Nigeria"), + NICARAGUA("NI", "Nicaragua"), + NETHERLANDS("NL", "Netherlands"), + NORWAY("NO", "Norway"), + NEPAL("NP", "Nepal"), + NAURU("NR", "Nauru"), + NIUE("NU", "Niue"), + NEW_ZEALAND("NZ", "New Zealand"), + OMAN("OM", "Oman"), + PANAMA("PA", "Panama"), + PERU("PE", "Peru"), + FRENCH_POLYNESIA("PF", "French Polynesia"), + PAPUA_NEW_GUINEA("PG", "Papua New Guinea"), + PHILIPPINES("PH", "Philippines"), + PAKISTAN("PK", "Pakistan"), + POLAND("PL", "Poland"), + SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"), + PITCAIRN("PN", "Pitcairn"), + PUERTO_RICO("PR", "Puerto Rico"), + PALESTINE("PS", "Palestine"), + PORTUGAL("PT", "Portugal"), + PALAU("PW", "Palau"), + PARAGUAY("PY", "Paraguay"), + QATAR("QA", "Qatar"), + REUNION("RE", "Reunion"), + ROMANIA("RO", "Romania"), + SERBIA("RS", "Serbia"), + RUSSIA("RU", "Russia"), + RWANDA("RW", "Rwanda"), + SAUDI_ARABIA("SA", "Saudi Arabia"), + SOLOMON_ISLANDS("SB", "Solomon Islands"), + SEYCHELLES("SC", "Seychelles"), + SUDAN("SD", "Sudan"), + SWEDEN("SE", "Sweden"), + SINGAPORE("SG", "Singapore"), + SAINT_HELENA("SH", "Saint Helena"), + SLOVENIA("SI", "Slovenia"), + SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"), + SLOVAKIA("SK", "Slovakia"), + SIERRA_LEONE("SL", "Sierra Leone"), + SAN_MARINO("SM", "San Marino"), + SENEGAL("SN", "Senegal"), + SOMALIA("SO", "Somalia"), + SURINAME("SR", "Suriname"), + SOUTH_SUDAN("SS", "South Sudan"), + SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"), + EL_SALVADOR("SV", "El Salvador"), + SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"), + SYRIA("SY", "Syria"), + SWAZILAND("SZ", "Swaziland"), + TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"), + CHAD("TD", "Chad"), + FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"), + TOGO("TG", "Togo"), + THAILAND("TH", "Thailand"), + TAJIKISTAN("TJ", "Tajikistan"), + TOKELAU("TK", "Tokelau"), + TIMOR_LESTE("TL", "Timor-Leste"), + TURKMENISTAN("TM", "Turkmenistan"), + TUNISIA("TN", "Tunisia"), + TONGA("TO", "Tonga"), + TURKEY("TR", "Turkey"), + TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"), + TUVALU("TV", "Tuvalu"), + TAIWAN("TW", "Taiwan"), + TANZANIA("TZ", "Tanzania"), + UKRAINE("UA", "Ukraine"), + UGANDA("UG", "Uganda"), + UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"), + UNITED_STATES("US", "United States"), + URUGUAY("UY", "Uruguay"), + UZBEKISTAN("UZ", "Uzbekistan"), + VATICAN("VA", "Vatican"), + SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"), + VENEZUELA("VE", "Venezuela"), + BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"), + U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"), + VIETNAM("VN", "Vietnam"), + VANUATU("VU", "Vanuatu"), + WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"), + SAMOA("WS", "Samoa"), + YEMEN("YE", "Yemen"), + MAYOTTE("YT", "Mayotte"), + SOUTH_AFRICA("ZA", "South Africa"), + ZAMBIA("ZM", "Zambia"), + ZIMBABWE("ZW", "Zimbabwe"); + + private String isoTag; + private String name; + + Country(String isoTag, String name) { + this.isoTag = isoTag; + this.name = name; + } + + /** + * Gets the name of the country. + * + * @return The name of the country. + */ + public String getCountryName() { + return name; + } + + /** + * Gets the iso tag of the country. + * + * @return The iso tag of the country. + */ + public String getCountryIsoTag() { + return isoTag; + } + + /** + * Gets a country by it's iso tag. + * + * @param isoTag The iso tag of the county. + * + * @return The country with the given iso tag or null if unknown. + */ + public static Country byIsoTag(String isoTag) { + for (Country country : Country.values()) { + if (country.getCountryIsoTag().equals(isoTag)) { + return country; + } + } + return null; + } + + /** + * Gets a country by a locale. + * + * @param locale The locale. + * + * @return The country from the giben locale or null if unknown country or + * if the locale does not contain a country. + */ + public static Country byLocale(Locale locale) { + return byIsoTag(locale.getCountry()); + } + } +} From 95945ffd22d7abf472fdcff1ece888ae72b9a3c4 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 Jan 2017 17:44:06 +0100 Subject: [PATCH 08/79] #761 Improve permissions group support - Drop one auth group type in favor of three: logged in, registered but not logged in, and unregistered - Move properties to same parent path --- .../authme/permission/AuthGroupHandler.java | 78 ++++++------------ .../authme/permission/AuthGroupType.java | 7 +- .../authme/permission/PermissionsManager.java | 82 ++----------------- .../authme/process/join/AsynchronousJoin.java | 2 +- .../ProcessSynchronousPlayerLogout.java | 2 +- .../register/ProcessSyncEmailRegister.java | 9 +- .../register/ProcessSyncPasswordRegister.java | 8 +- .../settings/SettingsMigrationService.java | 28 ++++++- .../settings/properties/HooksSettings.java | 7 -- .../settings/properties/PluginSettings.java | 30 +++++-- .../settings/properties/SecuritySettings.java | 16 ---- .../SettingsMigrationServiceTest.java | 6 ++ .../fr/xephi/authme/settings/config-old.yml | 2 +- 13 files changed, 99 insertions(+), 178 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index 537c3ebc..050b908a 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -5,14 +5,11 @@ import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.settings.Settings; -import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; -import java.util.Arrays; /** * Changes the permission group according to the auth status of the player and the configuration. @@ -28,7 +25,6 @@ public class AuthGroupHandler implements Reloadable { @Inject private LimboCache limboCache; - private String unloggedInGroup; private String unregisteredGroup; private String registeredGroup; @@ -36,15 +32,15 @@ public class AuthGroupHandler implements Reloadable { } /** - * Set the group of a player, by its AuthMe group type. + * Sets the group of a player by its authentication status. * - * @param player The player. - * @param group The group type. + * @param player the player + * @param groupType the group type * - * @return True if succeeded, false otherwise. False is also returned if groups aren't supported + * @return True upon success, false otherwise. False is also returned if groups aren't supported * with the current permissions system. */ - public boolean setGroup(Player player, AuthGroupType group) { + public boolean setGroup(Player player, AuthGroupType groupType) { // Check whether the permissions check is enabled if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { return false; @@ -56,71 +52,45 @@ public class AuthGroupHandler implements Reloadable { return false; } - switch (group) { + switch (groupType) { case UNREGISTERED: - // Remove the other group type groups, set the current group - permissionsManager.removeGroups(player, Arrays.asList(registeredGroup, unloggedInGroup)); + // Remove the other group, set the current group + permissionsManager.removeGroups(player, registeredGroup); return permissionsManager.addGroup(player, unregisteredGroup); - case REGISTERED: - // Remove the other group type groups, set the current group - permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, unloggedInGroup)); + case REGISTERED_UNAUTHENTICATED: + // Remove the other group, set the current group + permissionsManager.removeGroups(player, unregisteredGroup); return permissionsManager.addGroup(player, registeredGroup); - case NOT_LOGGED_IN: - // Remove the other group type groups, set the current group - permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup)); - return permissionsManager.addGroup(player, unloggedInGroup); - case LOGGED_IN: - // Get the player data - LimboPlayer data = limboCache.getPlayerData(player.getName().toLowerCase()); - if (data == null) { - return false; - } + return restoreGroup(player); - // Get the players group - String realGroup = data.getGroup(); - - // Remove the other group types groups, set the real group - permissionsManager.removeGroups(player, - Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup) - ); - return permissionsManager.addGroup(player, realGroup); default: - return false; + throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'"); } } - /** - * TODO: This method requires better explanation. - *

- * Set the normal group of a player. - * - * @param player The player. - * @param group The normal group. - * - * @return True on success, false on failure. - */ - public boolean addNormal(Player player, String group) { - // Check whether the permissions check is enabled - if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { + private boolean restoreGroup(Player player) { + // Get the player's LimboPlayer + LimboPlayer limbo = limboCache.getPlayerData(player.getName()); + if (limbo == null) { return false; } - // Remove old groups - permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup)); + // Get the players group + String realGroup = limbo.getGroup(); - // Add the normal group, return the result - return permissionsManager.addGroup(player, group); + // Remove the other group types groups, set the real group + permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup); + return permissionsManager.addGroup(player, realGroup); } @Override @PostConstruct public void reload() { - unloggedInGroup = settings.getProperty(SecuritySettings.UNLOGGEDIN_GROUP); - unregisteredGroup = settings.getProperty(HooksSettings.UNREGISTERED_GROUP); - registeredGroup = settings.getProperty(HooksSettings.REGISTERED_GROUP); + unregisteredGroup = settings.getProperty(PluginSettings.UNREGISTERED_GROUP); + registeredGroup = settings.getProperty(PluginSettings.REGISTERED_GROUP); } } diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupType.java b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java index 9ab2a370..dfedf8ee 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupType.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java @@ -8,11 +8,8 @@ public enum AuthGroupType { /** Player does not have an account. */ UNREGISTERED, - /** Registered? */ - REGISTERED, // TODO #761: Remove this or the NOT_LOGGED_IN one - - /** Player is registered and not logged in. */ - NOT_LOGGED_IN, + /** Player is registered but not logged in. */ + REGISTERED_UNAUTHENTICATED, /** Player is logged in. */ LOGGED_IN diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 7ce0b3e5..6af00070 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -289,7 +289,7 @@ public class PermissionsManager implements Reloadable { * @return True if the player is in the specified group, false otherwise. * False is also returned if groups aren't supported by the used permissions system. */ - public boolean inGroup(Player player, String groupName) { + public boolean isInGroup(Player player, String groupName) { // If no permissions system is used, return false if (!isEnabled()) return false; @@ -307,42 +307,12 @@ public class PermissionsManager implements Reloadable { * False is also returned if this feature isn't supported for the current permissions system. */ public boolean addGroup(Player player, String groupName) { - if (StringUtils.isEmpty(groupName)) { + if (!isEnabled() || StringUtils.isEmpty(groupName)) { return false; } - - // If no permissions system is used, return false - if (!isEnabled()) { - return false; - } - return handler.addToGroup(player, groupName); } - /** - * Add the permission groups of a player, if supported. - * - * @param player The player - * @param groupNames The name of the groups to add. - * - * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. - */ - public boolean addGroups(Player player, List groupNames) { - // If no permissions system is used, return false - if (!isEnabled()) - return false; - - // Add each group to the user - boolean result = true; - for (String groupName : groupNames) - if (!addGroup(player, groupName)) - result = false; - - // Return the result - return result; - } - /** * Remove the permission group of a player, if supported. * @@ -352,8 +322,7 @@ public class PermissionsManager implements Reloadable { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - public boolean removeGroup(Player player, String groupName) { - // If no permissions system is used, return false + public boolean removeGroups(Player player, String groupName) { if (!isEnabled()) return false; @@ -369,16 +338,18 @@ public class PermissionsManager implements Reloadable { * @return True if succeed, false otherwise. * False is also returned if this feature isn't supported for the current permissions system. */ - public boolean removeGroups(Player player, List groupNames) { + public boolean removeGroups(Player player, String... groupNames) { // If no permissions system is used, return false if (!isEnabled()) return false; // Add each group to the user boolean result = true; - for (String groupName : groupNames) - if (!removeGroup(player, groupName)) + for (String groupName : groupNames) { + if (!handler.removeFromGroup(player, groupName)) { result = false; + } + } // Return the result return result; @@ -402,41 +373,6 @@ public class PermissionsManager implements Reloadable { return handler.setGroup(player, groupName); } - /** - * Set the permission groups of a player, if supported. - * This clears the current groups of the player. - * - * @param player The player - * @param groupNames The name of the groups to set. - * - * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. - */ - public boolean setGroups(Player player, List groupNames) { - // If no permissions system is used or if there's no group supplied, return false - if (!isEnabled() || groupNames.isEmpty()) - return false; - - // Set the main group - if (!setGroup(player, groupNames.get(0))) - return false; - - // Add the rest of the groups - boolean result = true; - for (int i = 1; i < groupNames.size(); i++) { - // Get the group name - String groupName = groupNames.get(i); - - // Add this group - if (!addGroup(player, groupName)) { - result = false; - } - } - - // Return the result - return result; - } - /** * Remove all groups of the specified player, if supported. * Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay @@ -456,6 +392,6 @@ public class PermissionsManager implements Reloadable { List groupNames = getGroups(player); // Remove each group - return removeGroups(player, groupNames); + return removeGroups(player, groupNames.toArray(new String[groupNames.size()])); } } 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 dab9cdc2..0f7e819f 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -112,7 +112,7 @@ public class AsynchronousJoin implements AsynchronousProcess { if (isAuthAvailable) { limboCache.addPlayerData(player); - service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); + service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); // Protect inventory if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java index 17710df4..18229146 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -77,7 +77,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { } // Set player's data to unauthenticated - service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); + service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); player.setOp(false); player.setAllowFlight(false); // Remove speed 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 aea1b4be..cebbcdcb 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -3,9 +3,8 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SynchronousProcess; -import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -25,12 +24,10 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { } public void processEmailRegister(Player player) { - final String name = player.getName().toLowerCase(); - if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) { - service.setGroup(player, AuthGroupType.REGISTERED); - } + service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED); + final String name = player.getName().toLowerCase(); limboPlayerTaskManager.registerTimeoutTask(player); limboPlayerTaskManager.registerMessageTask(name, true); 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 fae428d0..0b06df61 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -4,12 +4,11 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BungeeService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; @@ -56,10 +55,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { } public void processPasswordRegister(Player player) { - if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) { - service.setGroup(player, AuthGroupType.REGISTERED); - } - + service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); service.send(player, MessageKey.REGISTER_SUCCESS); if (!service.getProperty(EmailSettings.MAIL_ACCOUNT).isEmpty()) { diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index aac60dde..e4b175b7 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -71,6 +71,7 @@ public class SettingsMigrationService extends PlainMigrationService { | hasOldHelpHeaderProperty(resource) | hasSupportOldPasswordProperty(resource) | convertToRegistrationType(resource) + | mergeAndMovePermissionGroupSettings(resource) || hasDeprecatedProperties(resource); } @@ -251,6 +252,26 @@ public class SettingsMigrationService extends PlainMigrationService { return true; } + private static boolean mergeAndMovePermissionGroupSettings(PropertyResource resource) { + boolean performedChanges; + + // We have two old settings replaced by only one: move the first non-empty one + Property oldUnloggedInGroup = newProperty("settings.security.unLoggedinGroup", ""); + Property oldRegisteredGroup = newProperty("GroupOptions.RegisteredPlayerGroup", ""); + if (!oldUnloggedInGroup.getValue(resource).isEmpty()) { + performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, resource); + } else { + performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, resource); + } + + // Move paths of other old options + performedChanges |= moveProperty(newProperty("GroupOptions.UnregisteredPlayerGroup", ""), + PluginSettings.UNREGISTERED_GROUP, resource); + performedChanges |= moveProperty(newProperty("permission.EnablePermissionCheck", false), + PluginSettings.ENABLE_PERMISSION_CHECK, resource); + return performedChanges; + } + /** * Checks for an old property path and moves it to a new path if present. * @@ -264,9 +285,10 @@ public class SettingsMigrationService extends PlainMigrationService { Property newProperty, PropertyResource resource) { if (resource.contains(oldProperty.getPath())) { - ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath()); - if (!resource.contains(newProperty.getPath())) { - ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath()); + if (resource.contains(newProperty.getPath())) { + ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath()); + } else { + ConsoleLogger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath()); resource.setValue(newProperty.getPath(), oldProperty.getValue(resource)); } return true; diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java index b1eaa222..f512d67b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -58,13 +58,6 @@ public class HooksSettings implements SettingsHolder { public static final Property WORDPRESS_TABLE_PREFIX = newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_"); - @Comment("Unregistered permission group") - public static final Property UNREGISTERED_GROUP = - newProperty("GroupOptions.UnregisteredPlayerGroup", ""); - - @Comment("Registered permission group") - public static final Property REGISTERED_GROUP = - newProperty("GroupOptions.RegisteredPlayerGroup", ""); private HooksSettings() { } 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 cfd717eb..eb6ffd6d 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java @@ -44,13 +44,33 @@ public class PluginSettings implements SettingsHolder { newProperty("settings.messagesLanguage", "en"); @Comment({ - "Take care with this option; if you want", - "to use group switching of AuthMe", - "for unloggedIn players, set this setting to true.", - "Default is false." + "Enables switching a player to defined permission groups before they log in.", + "See below for a detailed explanation." }) public static final Property ENABLE_PERMISSION_CHECK = - newProperty("permission.EnablePermissionCheck", false); + newProperty("GroupOptions.enablePermissionCheck", false); + + @Comment({ + "This is a very important option: if a registered player joins the server", + "AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits.", + "You can set up your permission plugin with this special group to have no permissions,", + "or only permission to chat (or permission to send private messages etc.).", + "The better way is to set up this group with few permissions, so if a player", + "tries to exploit an account they can do only what you've defined for the group.", + "After login, the player will be moved to his correct permissions group!", + "Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'", + "Otherwise your group will be wiped and the player will join in the default group []!", + "Example: registeredPlayerGroup: 'NotLogged'" + }) + public static final Property REGISTERED_GROUP = + newProperty("GroupOptions.registeredPlayerGroup", ""); + + @Comment({ + "Similar to above, unregistered players can be set to the following", + "permissions group" + }) + public static final Property UNREGISTERED_GROUP = + newProperty("GroupOptions.unregisteredPlayerGroup", ""); @Comment({ "Log level: INFO, FINE, DEBUG. Use INFO for general messages,", diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 5d3d870c..054651ad 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -48,22 +48,6 @@ public class SecuritySettings implements SettingsHolder { public static final Property MAX_PASSWORD_LENGTH = newProperty("settings.security.passwordMaxLength", 30); - @Comment({ - "This is a very important option: every time a player joins the server,", - "if they are registered, AuthMe will switch him to unLoggedInGroup.", - "This should prevent all major exploits.", - "You can set up your permission plugin with this special group to have no permissions,", - "or only permission to chat (or permission to send private messages etc.).", - "The better way is to set up this group with few permissions, so if a player", - "tries to exploit an account they can do only what you've defined for the group.", - "After, a logged in player will be moved to his correct permissions group!", - "Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'", - "Otherwise your group will be wiped and the player will join in the default group []!", - "Example unLoggedinGroup: NotLogged" - }) - public static final Property UNLOGGEDIN_GROUP = - newProperty("settings.security.unLoggedinGroup", "unLoggedinGroup"); - @Comment({ "Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,", "MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,", diff --git a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java index 67893666..da7c270b 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsMigrationServiceTest.java @@ -18,7 +18,10 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import static fr.xephi.authme.TestHelper.getJarFile; +import static fr.xephi.authme.settings.properties.PluginSettings.ENABLE_PERMISSION_CHECK; import static fr.xephi.authme.settings.properties.PluginSettings.LOG_LEVEL; +import static fr.xephi.authme.settings.properties.PluginSettings.REGISTERED_GROUP; +import static fr.xephi.authme.settings.properties.PluginSettings.UNREGISTERED_GROUP; import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE; import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTER_SECOND_ARGUMENT; import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTRATION_TYPE; @@ -65,6 +68,9 @@ public class SettingsMigrationServiceTest { assertThat(settings.getProperty(LOG_LEVEL), equalTo(LogLevel.INFO)); assertThat(settings.getProperty(REGISTRATION_TYPE), equalTo(RegistrationType.EMAIL)); assertThat(settings.getProperty(REGISTER_SECOND_ARGUMENT), equalTo(RegisterSecondaryArgument.CONFIRMATION)); + assertThat(settings.getProperty(ENABLE_PERMISSION_CHECK), equalTo(true)); + assertThat(settings.getProperty(REGISTERED_GROUP), equalTo("unLoggedinGroup")); + assertThat(settings.getProperty(UNREGISTERED_GROUP), equalTo("")); // Check migration of old setting to email.html assertThat(Files.readLines(new File(dataFolder, "email.html"), StandardCharsets.UTF_8), diff --git a/src/test/resources/fr/xephi/authme/settings/config-old.yml b/src/test/resources/fr/xephi/authme/settings/config-old.yml index 5181b3f7..65e2614c 100644 --- a/src/test/resources/fr/xephi/authme/settings/config-old.yml +++ b/src/test/resources/fr/xephi/authme/settings/config-old.yml @@ -290,7 +290,7 @@ permission: # to use Vault and Group Switching of # AuthMe for unloggedIn players put true # below, default is false. - EnablePermissionCheck: false + EnablePermissionCheck: true BackupSystem: # Enable or Disable Automatic Backup ActivateBackup: false From 350ef9b5e6eff53ed562ae081555be94e2ec87c9 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 Jan 2017 18:16:17 +0100 Subject: [PATCH 09/79] PermissionHandlers: add default methods for trivial duplications - Add easy default methods on PermissionHandler interface (override whenever there's a better way!) - Change getGroups() signature to return a Collection instead of a List --- .../authme/permission/PermissionsManager.java | 12 ++++----- .../handlers/BPermissionsHandler.java | 19 +++++--------- .../handlers/PermissionHandler.java | 17 +++++++++--- .../handlers/PermissionsBukkitHandler.java | 26 ++++--------------- .../handlers/PermissionsExHandler.java | 17 +++++------- .../permission/handlers/VaultHandler.java | 6 +++++ .../handlers/ZPermissionsHandler.java | 18 ++++++------- 7 files changed, 51 insertions(+), 64 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 6af00070..497c99ab 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -19,8 +19,8 @@ import org.bukkit.plugin.PluginManager; import javax.annotation.PostConstruct; import javax.inject.Inject; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.Collections; /** *

@@ -255,12 +255,12 @@ public class PermissionsManager implements Reloadable { * * @param player The player. * - * @return Permission groups, or an empty list if this feature is not supported. + * @return Permission groups, or an empty collection if this feature is not supported. */ - public List getGroups(Player player) { + public Collection getGroups(Player player) { // If no permissions system is used, return an empty list if (!isEnabled()) - return new ArrayList<>(); + return Collections.emptyList(); return handler.getGroups(player); } @@ -389,7 +389,7 @@ public class PermissionsManager implements Reloadable { return false; // Get a list of current groups - List groupNames = getGroups(player); + Collection groupNames = getGroups(player); // Remove each group return removeGroups(player, groupNames.toArray(new String[groupNames.size()])); diff --git a/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java index 9f52214b..849ecd65 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java @@ -9,6 +9,12 @@ import org.bukkit.entity.Player; import java.util.Arrays; import java.util.List; +/** + * Handler for bPermissions. + * + * @see bPermissions Bukkit page + * @see bPermissions on Github + */ public class BPermissionsHandler implements PermissionHandler { @Override @@ -49,19 +55,6 @@ public class BPermissionsHandler implements PermissionHandler { return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName())); } - @Override - public String getPrimaryGroup(Player player) { - // Get the groups of the player - List groups = getGroups(player); - - // Make sure there is any group available, or return null - if (groups.isEmpty()) - return null; - - // Return the first group - return groups.get(0); - } - @Override public PermissionsSystemType getPermissionSystem() { return PermissionsSystemType.B_PERMISSIONS; diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java index 71c7a287..36f6497c 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java @@ -2,9 +2,10 @@ package fr.xephi.authme.permission.handlers; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsSystemType; +import fr.xephi.authme.util.Utils; import org.bukkit.entity.Player; -import java.util.List; +import java.util.Collection; public interface PermissionHandler { @@ -48,7 +49,9 @@ public interface PermissionHandler { * @return True if the player is in the specified group, false otherwise. * False is also returned if groups aren't supported by the used permissions system. */ - boolean isInGroup(Player player, String group); + default boolean isInGroup(Player player, String group) { + return getGroups(player).contains(group); + } /** * Remove the permission group of a player, if supported. @@ -80,7 +83,7 @@ public interface PermissionHandler { * * @return Permission groups, or an empty list if this feature is not supported. */ - List getGroups(Player player); + Collection getGroups(Player player); /** * Get the primary group of a player, if available. @@ -89,7 +92,13 @@ public interface PermissionHandler { * * @return The name of the primary permission group. Or null. */ - String getPrimaryGroup(Player player); + default String getPrimaryGroup(Player player) { + Collection groups = getGroups(player); + if (Utils.isCollectionEmpty(groups)) { + return null; + } + return groups.iterator().next(); + } /** * Get the permission system that is being used. diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java index c215b8d1..b7773e7a 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java @@ -12,6 +12,11 @@ import org.bukkit.plugin.PluginManager; import java.util.ArrayList; import java.util.List; +/** + * Handler for PermissionsBukkit. + * + * @see PermissionsBukkit Bukkit page + */ public class PermissionsBukkitHandler implements PermissionHandler { private PermissionsPlugin permissionsBukkitInstance; @@ -39,13 +44,6 @@ public class PermissionsBukkitHandler implements PermissionHandler { return false; } - @Override - public boolean isInGroup(Player player, String group) { - List groupNames = getGroups(player); - - return groupNames.contains(group); - } - @Override public boolean removeFromGroup(Player player, String group) { return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + group); @@ -65,20 +63,6 @@ public class PermissionsBukkitHandler implements PermissionHandler { return groups; } - @Override - public String getPrimaryGroup(Player player) { - // Get the groups of the player - List groups = getGroups(player); - - // Make sure there is any group available, or return null - if (groups.isEmpty()) { - return null; - } - - // Return the first group - return groups.get(0); - } - @Override public PermissionsSystemType getPermissionSystem() { return PermissionsSystemType.PERMISSIONS_BUKKIT; diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java index b11d0000..9b4550d8 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java @@ -10,6 +10,12 @@ import ru.tehkode.permissions.bukkit.PermissionsEx; import java.util.ArrayList; import java.util.List; +/** + * Handler for PermissionsEx. + * + * @see PermissionsEx Bukkit page + * @see PermissionsEx on Github + */ public class PermissionsExHandler implements PermissionHandler { private PermissionManager permissionManager; @@ -72,17 +78,6 @@ public class PermissionsExHandler implements PermissionHandler { return user.getParentIdentifiers(null); } - @Override - public String getPrimaryGroup(Player player) { - PermissionUser user = permissionManager.getUser(player); - - List groups = user.getParentIdentifiers(null); - if (groups.isEmpty()) - return null; - - return groups.get(0); - } - @Override public PermissionsSystemType getPermissionSystem() { return PermissionsSystemType.PERMISSIONS_EX; diff --git a/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java index 8955569e..79badc53 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java @@ -10,6 +10,12 @@ import org.bukkit.plugin.RegisteredServiceProvider; import java.util.Arrays; import java.util.List; +/** + * Handler for permissions via Vault. + * + * @see Vault Bukkit page + * @see Vault on Github + */ public class VaultHandler implements PermissionHandler { private Permission vaultProvider; diff --git a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java index 498ba4a9..1de19f1d 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java @@ -6,10 +6,15 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; import java.util.Map; +/** + * Handler for zPermissions. + * + * @see zPermissions Bukkit page + * @see zPermissions on Github + */ public class ZPermissionsHandler implements PermissionHandler { private ZPermissionsService zPermissionsService; @@ -42,11 +47,6 @@ public class ZPermissionsHandler implements PermissionHandler { return false; } - @Override - public boolean isInGroup(Player player, String group) { - return getGroups(player).contains(group); - } - @Override public boolean removeFromGroup(Player player, String group) { return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + group); @@ -58,9 +58,9 @@ public class ZPermissionsHandler implements PermissionHandler { } @Override - public List getGroups(Player player) { + public Collection getGroups(Player player) { // TODO Gnat008 20160631: Use UUID not name? - return new ArrayList(zPermissionsService.getPlayerGroups(player.getName())); + return zPermissionsService.getPlayerGroups(player.getName()); } @Override From 24162ad94b1f79562775baa99ec3eaf76f221866 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 29 Jan 2017 19:47:14 +0100 Subject: [PATCH 10/79] #1034 Create debug command structure + utility to see permission groups - Relevant to current work... :) --- .../authme/command/CommandInitializer.java | 15 ++++++ .../executable/authme/debug/DebugCommand.java | 46 +++++++++++++++++++ .../executable/authme/debug/DebugSection.java | 30 ++++++++++++ .../authme/debug/PermissionGroups.java | 40 ++++++++++++++++ .../permission/PlayerStatePermission.java | 7 ++- src/main/resources/plugin.yml | 7 ++- .../command/CommandInitializerTest.java | 29 ------------ 7 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 7e01e0e3..98030c96 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -24,6 +24,7 @@ import fr.xephi.authme.command.executable.authme.SpawnCommand; import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand; import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand; import fr.xephi.authme.command.executable.authme.VersionCommand; +import fr.xephi.authme.command.executable.authme.debug.DebugCommand; import fr.xephi.authme.command.executable.captcha.CaptchaCommand; import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand; import fr.xephi.authme.command.executable.email.AddEmailCommand; @@ -37,6 +38,7 @@ import fr.xephi.authme.command.executable.register.RegisterCommand; import fr.xephi.authme.command.executable.unregister.UnregisterCommand; import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.permission.PlayerStatePermission; import java.util.Arrays; import java.util.Collection; @@ -298,6 +300,19 @@ public class CommandInitializer { .executableCommand(MessagesCommand.class) .register(); + CommandDescription.builder() + .parent(AUTHME_BASE) + .labels("debug", "dbg") + .description("Debug features") + .detailedDescription("Allows various operations for debugging.") + .withArgument("child", "The child to execute", true) + .withArgument(".", "meaning varies", true) + .withArgument(".", "meaning varies", true) + .withArgument(".", "meaning varies", true) + .permission(PlayerStatePermission.DEBUG_COMMAND) + .executableCommand(DebugCommand.class) + .register(); + // Register the base login command final CommandDescription LOGIN_BASE = CommandDescription.builder() .parent(null) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java new file mode 100644 index 00000000..f9541deb --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -0,0 +1,46 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.command.ExecutableCommand; +import org.bukkit.command.CommandSender; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Debug command main. + */ +public class DebugCommand implements ExecutableCommand { + + @Inject + private PermissionGroups permissionGroups; + + private Map sections; + + @PostConstruct + private void collectSections() { + Map sections = Stream.of(permissionGroups) + .collect(Collectors.toMap(DebugSection::getName, Function.identity())); + this.sections = sections; + } + + @Override + public void executeCommand(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("Available sections:"); + sections.values() + .forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription())); + } else { + DebugSection debugSection = sections.get(arguments.get(0).toLowerCase()); + if (debugSection == null) { + sender.sendMessage("Unknown subcommand"); + } else { + debugSection.execute(sender, arguments.subList(1, arguments.size())); + } + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java new file mode 100644 index 00000000..1f038983 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import org.bukkit.command.CommandSender; + +import java.util.List; + +/** + * A debug section: "child" command of the debug command. + */ +interface DebugSection { + + /** + * @return the name to get to this child command + */ + String getName(); + + /** + * @return short description of the child command + */ + String getDescription(); + + /** + * Executes the debug child command. + * + * @param sender the sender executing the command + * @param arguments the arguments, without the label of the child command + */ + void execute(CommandSender sender, List arguments); + +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java new file mode 100644 index 00000000..e3876ff0 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java @@ -0,0 +1,40 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.permission.PermissionsManager; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +/** + * Outputs the permission groups of a player. + */ +class PermissionGroups implements DebugSection { + + @Inject + private PermissionsManager permissionsManager; + + @Override + public String getName() { + return "groups"; + } + + @Override + public String getDescription() { + return "Show permission groups a player belongs to"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + String name = arguments.isEmpty() ? sender.getName() : arguments.get(0); + Player player = Bukkit.getPlayer(name); + if (player == null) { + sender.sendMessage("Player " + name + " could not be found"); + } else { + sender.sendMessage("Player " + name + " has permission groups: " + + String.join(", ", permissionsManager.getGroups(player))); + } + } +} diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java index aaeb0eea..2160eea5 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java @@ -29,7 +29,12 @@ public enum PlayerStatePermission implements PermissionNode { /** * Permission to bypass the purging process. */ - BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED); + BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED), + + /** + * Permission to use the /authme debug command. + */ + DEBUG_COMMAND("authme.debug", DefaultPermission.OP_ONLY); /** * The permission node. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9ca68c31..f04829d4 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,7 +17,7 @@ softdepend: commands: authme: description: AuthMe op commands - usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages + usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug login: description: Login command usage: /login @@ -153,6 +153,9 @@ permissions: authme.bypasspurge: description: Permission to bypass the purging process. default: false + authme.debug: + description: Permission to use the /authme debug command. + default: op authme.player.*: description: Gives access to all player commands children: @@ -207,5 +210,5 @@ permissions: description: Command permission to unregister. default: true authme.vip: - description: Permission node to identify VIP users. + description: When the server is full and someone with this permission joins the server, someone will be kicked. default: op diff --git a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java index a4e51e28..23bb2458 100644 --- a/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandInitializerTest.java @@ -1,7 +1,5 @@ package fr.xephi.authme.command; -import fr.xephi.authme.permission.AdminPermission; -import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.util.StringUtils; import org.junit.BeforeClass; import org.junit.Test; @@ -16,7 +14,6 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.regex.Pattern; -import static fr.xephi.authme.permission.DefaultPermission.OP_ONLY; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; @@ -199,32 +196,6 @@ public class CommandInitializerTest { walkThroughCommands(commands, noArgumentForParentChecker); } - /** - * Test that commands defined with the OP_ONLY default permission have at least one admin permission node. - */ - @Test - public void shouldNotHavePlayerPermissionIfDefaultsToOpOnly() { - // given - BiConsumer adminPermissionChecker = new BiConsumer() { - @Override - public void accept(CommandDescription command, Integer depth) { - PermissionNode permission = command.getPermission(); - if (permission != null && OP_ONLY.equals(permission.getDefaultPermission()) - && !hasAdminNode(permission)) { - fail("The command with labels " + command.getLabels() + " has OP_ONLY default " - + "permission but no permission node on admin level"); - } - } - - private boolean hasAdminNode(PermissionNode permission) { - return permission instanceof AdminPermission; - } - }; - - // when/then - walkThroughCommands(commands, adminPermissionChecker); - } - /** * Tests that multiple CommandDescription instances pointing to the same ExecutableCommand use the same * count of arguments. From 5305ff4f42d74b6249d39367f170b680f046059d Mon Sep 17 00:00:00 2001 From: Platinteufel Date: Mon, 30 Jan 2017 16:05:24 +0100 Subject: [PATCH 11/79] Update help_de.yml (#216) [AuthMe] Error getting message with key 'common.header'. Please update your config file 'help_de.yml' (or run /authme messages) --- src/main/resources/messages/help_de.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/messages/help_de.yml b/src/main/resources/messages/help_de.yml index 46292e16..b1841ef4 100644 --- a/src/main/resources/messages/help_de.yml +++ b/src/main/resources/messages/help_de.yml @@ -1,4 +1,5 @@ common: + header: '==========[ AuthMeReloaded Hilfe ]==========' optional: 'Optional' hasPermission: 'Du hast Berechtigung' noPermission: 'Keine Berechtigung' From f6b08ece68d1f392f814012a776a7a00f7f59ee5 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 30 Jan 2017 21:50:44 +0100 Subject: [PATCH 12/79] Trivial code householding - Convert field to local variable - Remove unused constructor parameter - Move limbo class into limbo package --- src/main/java/fr/xephi/authme/data/limbo/LimboCache.java | 1 - .../authme/data/{backup => limbo}/LimboPlayerStorage.java | 5 ++--- .../xephi/authme/initialization/OnShutdownPlayerSaver.java | 2 +- .../authme/process/quit/ProcessSyncronousPlayerQuit.java | 2 +- src/main/java/fr/xephi/authme/service/AntiBotService.java | 3 +-- src/main/java/fr/xephi/authme/settings/SpawnLoader.java | 7 ++----- .../java/fr/xephi/authme/data/limbo/LimboCacheTest.java | 1 - .../data/{backup => limbo}/LimboPlayerStorageTest.java | 5 ++--- .../java/fr/xephi/authme/settings/SpawnLoaderTest.java | 6 +----- 9 files changed, 10 insertions(+), 22 deletions(-) rename src/main/java/fr/xephi/authme/data/{backup => limbo}/LimboPlayerStorage.java (98%) rename src/test/java/fr/xephi/authme/data/{backup => limbo}/LimboPlayerStorageTest.java (98%) diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java index 893aba22..622ed481 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java @@ -1,6 +1,5 @@ package fr.xephi.authme.data.limbo; -import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; diff --git a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java similarity index 98% rename from src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java rename to src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java index 1b227028..dd2790bc 100644 --- a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.data.backup; +package fr.xephi.authme.data.limbo; import com.google.common.io.Files; import com.google.gson.Gson; @@ -10,11 +10,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.Location; diff --git a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java index 1e786244..11e9d6af 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java +++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java @@ -2,7 +2,7 @@ package fr.xephi.authme.initialization; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.backup.LimboPlayerStorage; +import fr.xephi.authme.data.limbo.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.service.PluginHookService; diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java index 73db67f8..47bfb912 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,6 +1,6 @@ package fr.xephi.authme.process.quit; -import fr.xephi.authme.data.backup.LimboPlayerStorage; +import fr.xephi.authme.data.limbo.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.process.SynchronousProcess; import org.bukkit.entity.Player; diff --git a/src/main/java/fr/xephi/authme/service/AntiBotService.java b/src/main/java/fr/xephi/authme/service/AntiBotService.java index 91f80041..90c62a24 100644 --- a/src/main/java/fr/xephi/authme/service/AntiBotService.java +++ b/src/main/java/fr/xephi/authme/service/AntiBotService.java @@ -30,7 +30,6 @@ public class AntiBotService implements SettingsDependent { // Settings private int duration; private int sensibility; - private int delay; private int interval; // Service status private AntiBotStatus antiBotStatus; @@ -60,7 +59,6 @@ public class AntiBotService implements SettingsDependent { // Load settings duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION); sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY); - delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY); interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL); // Stop existing protection @@ -77,6 +75,7 @@ public class AntiBotService implements SettingsDependent { // Delay the schedule on first start if (startup) { + int delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY); bukkitService.scheduleSyncDelayedTask(enableTask, delay * TICKS_PER_SECOND); startup = false; } else { diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java index ac1742fa..26f7d03b 100644 --- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java +++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java @@ -1,10 +1,9 @@ package fr.xephi.authme.settings; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.FileUtils; @@ -43,11 +42,9 @@ public class SpawnLoader implements Reloadable { * @param pluginFolder The AuthMe data folder * @param settings The setting instance * @param pluginHookService The plugin hooks instance - * @param dataSource The plugin auth database instance */ @Inject - SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService, - DataSource dataSource) { + SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) { // TODO ljacqu 20160312: Check if resource could be copied and handle the case if not File spawnFile = new File(pluginFolder, "spawn.yml"); FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java index 8eb8e959..fcc45451 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java @@ -1,7 +1,6 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; diff --git a/src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java similarity index 98% rename from src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java rename to src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java index f9c5880c..6d346854 100644 --- a/src/test/java/fr/xephi/authme/data/backup/LimboPlayerStorageTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java @@ -1,14 +1,13 @@ -package fr.xephi.authme.data.backup; +package fr.xephi.authme.data.limbo; import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.util.FileUtils; import org.bukkit.Location; import org.bukkit.World; diff --git a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java index 1ca06911..edd56192 100644 --- a/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java +++ b/src/test/java/fr/xephi/authme/settings/SpawnLoaderTest.java @@ -5,9 +5,8 @@ import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import com.google.common.io.Files; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; import org.bukkit.World; @@ -38,9 +37,6 @@ public class SpawnLoaderTest { @Mock private Settings settings; - @Mock - private DataSource dataSource; - @Mock private PluginHookService pluginHookService; From 12c25562775540dc846a0dde6882021c31bd8759 Mon Sep 17 00:00:00 2001 From: Playhi <000902play@gmail.com> Date: Thu, 2 Feb 2017 12:34:08 -0600 Subject: [PATCH 13/79] Update messages_zhcn.yml (#217) Update messages_zhcn.yml --- src/main/resources/messages/messages_zhcn.yml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index f52f9b41..e6c5e755 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -2,7 +2,7 @@ reg_msg: '&8[&6玩家系统&8] &c请输入“/register <密码> <再输入一次以确定密码>”以注册' usage_reg: '&8[&6玩家系统&8] &c正确用法:“/register <密码> <再输入一次以确定密码>”' reg_only: '&8[&6玩家系统&8] &f只允许注册过的玩家进服!请到 https://example.cn 注册' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +kicked_admin_registered: '有一位管理员刚刚为您完成了注册,请重新登录' registered: '&8[&6玩家系统&8] &c已成功注册!' reg_disabled: '&8[&6玩家系统&8] &c目前服务器暂时禁止注册,请到服务器论坛以得到更多资讯' user_regged: '&8[&6玩家系统&8] &c此用户已经在此服务器注册过' @@ -11,7 +11,7 @@ user_regged: '&8[&6玩家系统&8] &c此用户已经在此服务器注册过' password_error: '&8[&6玩家系统&8] &f密码不相同' password_error_nick: '&8[&6玩家系统&8] &f你不能使用你的名字作为密码。 ' password_error_unsafe: '&8[&6玩家系统&8] &f你不能使用安全性过低的码。 ' -# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +password_error_chars: '&4您的密码包含了非法字符。可使用的字符: REG_EX' pass_len: '&8[&6玩家系统&8] &你的密码没有达到要求!' # Login @@ -23,10 +23,10 @@ timeout: '&8[&6玩家系统&8] &f给你登录的时间已经过了' # Errors unknown_user: '&8[&6玩家系统&8] &c此用户名还未注册过' -# TODO denied_command: '&cIn order to use this command you must be authenticated!' -# TODO denied_chat: '&cIn order to chat you must be authenticated!' +denied_command: '&c您需要先通过验证才能使用该命令!' +denied_chat: '&c您需要先通过验证才能聊天!' not_logged_in: '&8[&6玩家系统&8] &c你还未登录!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&c由于您登录失败次数过多,已被暂时禁止登录。' # TODO: Missing tags %reg_count, %max_acc, %reg_names max_reg: '&8[&6玩家系统&8] &f你不允许再为你的IP在服务器注册更多用户了!' no_perm: '&8[&6玩家系统&8] &c没有权限' @@ -41,11 +41,11 @@ antibot_auto_disabled: '&8[&6玩家系统&8] &f防机器人程序由于异常连 # Other messages unregistered: '&8[&6玩家系统&8] &c成功删除此用户!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: '您拥有 %count 个账户:' +accounts_owned_other: '玩家 %name 拥有 %count 个账户:' two_factor_create: '&8[&6玩家系统&8] &2你的代码是 %code,你可以使用 %url 来扫描' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +recovery_code_sent: '一个用于重置您的密码的验证码已发到您的邮箱' +recovery_code_incorrect: '验证码不正确! 使用 /email recovery [email] 以生成新的验证码' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' usage_unreg: '&8[&6玩家系统&8] &c正确用法:“/unregister <密码>”' pwd_changed: '&8[&6玩家系统&8] &c密码已成功修改!' @@ -66,7 +66,7 @@ not_owner_error: '&8[&6玩家系统&8] &4警告! &c你并不是此帐户持有 kick_fullserver: '&8[&6玩家系统&8] &c抱歉,服务器已满!' same_nick: '&8[&6玩家系统&8] &f同样的用户名现在在线且已经登录了!' invalid_name_case: '&8[&6玩家系统&8] &c你应该使用「%valid」而并非「%invalid」登入游戏。 ' -# TODO same_ip_online: 'A player with the same IP is already in game!' +same_ip_online: '已有一个同IP玩家在游戏中了!' # Email usage_email_add: '&8[&6玩家系统&8] &f用法: /email add <邮箱> <确认电子邮件> ' @@ -80,11 +80,11 @@ email_confirm: '&8[&6玩家系统&8] &f确认你的邮箱 !' email_changed: '&8[&6玩家系统&8] &f邮箱已改变 !' email_send: '&8[&6玩家系统&8] &f恢复邮件已发送 !' email_exists: '&8[&6玩家系统&8] &c恢复邮件已发送 ! 你可以丢弃它然後使用以下的指令来发送新的邮件:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2您当前的电子邮件地址为: &f%email' +incomplete_email_settings: '错误:并非所有发送邮件需要的设置都已被设置,请联系管理员' email_already_used: '&8[&6玩家系统&8] &4邮箱已被使用' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' +email_send_failure: '邮件发送失败,请联系管理员' +show_no_email: '&2您当前并没有任何邮箱与该账号绑定' add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号' recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”' From 5ad528d5d92537eb11918f28eb7ce612b9156b6a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 4 Feb 2017 11:35:22 +0100 Subject: [PATCH 14/79] Fix Javadoc errors (as seen in Jenkins build) --- .../fr/xephi/authme/message/MessageKey.java | 30 +++++++++---------- .../util/lazytags/WrappedTagReplacer.java | 2 +- .../messages/AddJavaDocToMessageEnumTask.java | 3 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 6cc57480..7546a89c 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -17,7 +17,7 @@ public enum MessageKey { /** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */ KICK_ANTIBOT("kick_antibot"), - /** Can't find the requested user in the database! */ + /** This user isn't registered! */ UNKNOWN_USER("unknown_user"), /** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */ @@ -26,7 +26,7 @@ public enum MessageKey { /** You're not logged in! */ NOT_LOGGED_IN("not_logged_in"), - /** Usage: /login <password> */ + /** Usage: /login <password> */ USAGE_LOGIN("usage_log"), /** Wrong password! */ @@ -56,19 +56,19 @@ public enum MessageKey { /** An unexpected error occurred, please contact an administrator! */ ERROR("error"), - /** Please, login with the command "/login <password>" */ + /** Please, login with the command: /login <password> */ LOGIN_MESSAGE("login_msg"), - /** Please, register to the server with the command "/register <password> <ConfirmPassword>" */ + /** Please, register to the server with the command: /register <password> <ConfirmPassword> */ REGISTER_MESSAGE("reg_msg"), /** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */ MAX_REGISTER_EXCEEDED("max_reg", "%max_acc", "%reg_count", "%reg_names"), - /** Usage: /register <password> <ConfirmPassword> */ + /** Usage: /register <password> <ConfirmPassword> */ USAGE_REGISTER("usage_reg"), - /** Usage: /unregister <password> */ + /** Usage: /unregister <password> */ USAGE_UNREGISTER("usage_unreg"), /** Password changed successfully! */ @@ -95,7 +95,7 @@ public enum MessageKey { /** You're already logged in! */ ALREADY_LOGGED_IN_ERROR("logged_in"), - /** Logged-out successfully! */ + /** Logged out successfully! */ LOGOUT_SUCCESS("logout"), /** The same username is already playing on the server! */ @@ -113,7 +113,7 @@ public enum MessageKey { /** Login timeout exceeded, you have been kicked from the server, please try again! */ LOGIN_TIMEOUT_ERROR("timeout"), - /** Usage: /changepassword <oldPassword> <newPassword> */ + /** Usage: /changepassword <oldPassword> <newPassword> */ USAGE_CHANGE_PASSWORD("usage_changepassword"), /** Your username is either too short or too long! */ @@ -122,13 +122,13 @@ public enum MessageKey { /** Your username contains illegal characters. Allowed chars: REG_EX */ INVALID_NAME_CHARACTERS("regex", "REG_EX"), - /** Please add your email to your account with the command "/email add <yourEmail> <confirmEmail>" */ + /** Please add your email to your account with the command: /email add <yourEmail> <confirmEmail> */ ADD_EMAIL_MESSAGE("add_email"), - /** Forgot your password? Please use the command "/email recovery <yourEmail>" */ + /** Forgot your password? Please use the command: /email recovery <yourEmail> */ FORGOT_PASSWORD_MESSAGE("recovery_email"), - /** To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>" */ + /** To login you have to solve a captcha code, please use the command: /captcha <theCaptcha> */ USAGE_CAPTCHA("usage_captcha", ""), /** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */ @@ -143,13 +143,13 @@ public enum MessageKey { /** The server is full, try again later! */ KICK_FULL_SERVER("kick_fullserver"), - /** Usage: /email add <email> <confirmEmail> */ + /** Usage: /email add <email> <confirmEmail> */ USAGE_ADD_EMAIL("usage_email_add"), - /** Usage: /email change <oldEmail> <newEmail> */ + /** Usage: /email change <oldEmail> <newEmail> */ USAGE_CHANGE_EMAIL("usage_email_change"), - /** Usage: /email recovery <Email> */ + /** Usage: /email recovery <Email> */ USAGE_RECOVER_EMAIL("usage_email_recovery"), /** Invalid new email, try again! */ @@ -224,7 +224,7 @@ public enum MessageKey { /** A recovery code to reset your password has been sent to your email. */ RECOVERY_CODE_SENT("recovery_code_sent"), - /** The recovery code is not correct! Use /email recovery [email] to generate a new one */ + /** The recovery code is not correct! Use "/email recovery [email]" to generate a new one */ INCORRECT_RECOVERY_CODE("recovery_code_incorrect"); private String key; diff --git a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java index 92c3f70d..ce9487e2 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java @@ -28,7 +28,7 @@ public class WrappedTagReplacer { * @param allTags all available tags * @param items the items to apply the replacements on * @param stringGetter getter of the String property to adapt on the items - * @param itemCreator a function of signature (T, String) -> T: the original item and the adapted String are passed + * @param itemCreator a function taking (T, String): the original item and the adapted String, returning a new item */ public WrappedTagReplacer(Collection> allTags, Collection items, diff --git a/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java b/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java index 30a63244..44306dc8 100644 --- a/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java +++ b/src/test/java/tools/messages/AddJavaDocToMessageEnumTask.java @@ -45,6 +45,7 @@ public class AddJavaDocToMessageEnumTask implements AutoToolTask { return configuration.getString(key.getKey()) .replaceAll("&[0-9a-f]", "") .replace("&", "&") - .replace("<", "<"); + .replace("<", "<") + .replace(">", ">"); } } From 9425cc7fc31463af38e9e8212c1ab6a635cecce4 Mon Sep 17 00:00:00 2001 From: Dmitry Rendov Date: Sat, 4 Feb 2017 12:59:01 +0100 Subject: [PATCH 15/79] 1085 - added help_ru.yml (cherry picked from commit 921a663) --- src/main/resources/messages/help_ru.yml | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/resources/messages/help_ru.yml diff --git a/src/main/resources/messages/help_ru.yml b/src/main/resources/messages/help_ru.yml new file mode 100644 index 00000000..20d6bd9a --- /dev/null +++ b/src/main/resources/messages/help_ru.yml @@ -0,0 +1,45 @@ +# Translation config for the AuthMe help, e.g. when /authme help or /authme help register is called + +# ------------------------------------------------------- +# List of texts used in the help section +common: + header: '==========[ AuthMeReloaded Справка ]==========' + optional: 'Опционально' + hasPermission: 'У вас есть такие права' + noPermission: 'Нет прав' + default: 'По-умолчанию' + result: 'Результат' + defaultPermissions: + notAllowed: 'Нет прав' + opOnly: 'Только Операторы' + allowed: 'Разрешено всем' + +# ------------------------------------------------------- +# Titles of the individual help sections +# Set the translation text to empty text to disable the section, e.g. to hide alternatives: +# alternatives: '' +section: + command: 'Команда' + description: 'Краткое описание' + detailedDescription: 'Детальное описание' + arguments: 'Аргументы' + permissions: 'Разрешения' + alternatives: 'Альтернативы' + children: 'Команды' + +# ------------------------------------------------------- +# You can translate the data for all commands using the below pattern. +# For example to translate /authme reload, create a section "authme.reload", or "login" for /login +# If the command has arguments, you can use arg1 as below to translate the first argument, and so forth +# Translations don't need to be complete; any missing section will be taken from the default silently +# Important: Put main commands like "authme" before their children (e.g. "authme.reload") +commands: + authme.register: + description: 'Регистрация новго игрока' + detailedDescription: 'Регистрация игрока с указанным именем и паролем.' + arg1: + label: 'player' + description: 'Имя игрока' + arg2: + label: 'password' + description: 'Пароль' From 20cd9e95885fe070c73702a3f3d0e198cd95b0c2 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 4 Feb 2017 22:00:53 +0100 Subject: [PATCH 16/79] #1085 Improve of help translation files - Avoid logging an error if a help_{lang}.yml file does not exist in the JAR - No longer suggest /authme messages for updating the help translation - Create consistency test to ensure that all help_{lang}.yml files in the JAR have entries for all help sections / messages / default permissions --- .../authme/message/MessageFileHandler.java | 12 ++- .../message/MessageFileHandlerProvider.java | 18 +++- .../fr/xephi/authme/message/Messages.java | 2 +- src/main/resources/messages/help_br.yml | 1 + .../command/help/HelpMessagesServiceTest.java | 4 +- .../message/HelpMessageConsistencyTest.java | 93 +++++++++++++++++++ .../message/MessagesIntegrationTest.java | 4 +- 7 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java index a0d4e947..5d19e8f4 100644 --- a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java +++ b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java @@ -1,6 +1,7 @@ package fr.xephi.authme.message; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.util.FileUtils; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -16,6 +17,7 @@ public class MessageFileHandler { // regular file private final String filename; private final FileConfiguration configuration; + private final String updateAddition; // default file private final String defaultFile; private FileConfiguration defaultConfiguration; @@ -25,11 +27,15 @@ public class MessageFileHandler { * * @param file the file to use for messages * @param defaultFile the default file from the JAR to use if no message is found + * @param updateCommand command to update the messages file (nullable) to show in error messages */ - public MessageFileHandler(File file, String defaultFile) { + public MessageFileHandler(File file, String defaultFile, String updateCommand) { this.filename = file.getName(); this.configuration = YamlConfiguration.loadConfiguration(file); this.defaultFile = defaultFile; + this.updateAddition = updateCommand == null + ? "" + : " (or run " + updateCommand + ")"; } /** @@ -53,7 +59,7 @@ public class MessageFileHandler { if (message == null) { ConsoleLogger.warning("Error getting message with key '" + key + "'. " - + "Please update your config file '" + filename + "' (or run /authme messages)"); + + "Please update your config file '" + filename + "'" + updateAddition); return getDefault(key); } return message; @@ -78,7 +84,7 @@ public class MessageFileHandler { */ private String getDefault(String key) { if (defaultConfiguration == null) { - InputStream stream = MessageFileHandler.class.getClassLoader().getResourceAsStream(defaultFile); + InputStream stream = FileUtils.getResourceFromJar(defaultFile); defaultConfiguration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream)); } String message = defaultConfiguration.getString(key); diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java index 987caecc..5b809b2c 100644 --- a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java +++ b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java @@ -36,10 +36,23 @@ public class MessageFileHandlerProvider { * @return the message file handler */ public MessageFileHandler initializeHandler(Function pathBuilder) { + return initializeHandler(pathBuilder, null); + } + + /** + * Initializes a message file handler with the messages file of the configured language. + * Ensures beforehand that the messages file exists or creates it otherwise. + * + * @param pathBuilder function taking the configured language code as argument and returning the messages file + * @param updateCommand command to run to update the languages file (nullable) + * @return the message file handler + */ + public MessageFileHandler initializeHandler(Function pathBuilder, String updateCommand) { String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE); return new MessageFileHandler( initializeFile(language, pathBuilder), - pathBuilder.apply(DEFAULT_LANGUAGE)); + pathBuilder.apply(DEFAULT_LANGUAGE), + updateCommand); } /** @@ -53,7 +66,8 @@ public class MessageFileHandlerProvider { File initializeFile(String language, Function pathBuilder) { String filePath = pathBuilder.apply(language); File file = new File(dataFolder, filePath); - if (FileUtils.copyFileFromResource(file, filePath)) { + // Check that JAR file exists to avoid logging an error + if (FileUtils.getResourceFromJar(filePath) != null && FileUtils.copyFileFromResource(file, filePath)) { return file; } diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java index 86fa2db6..5d777ab8 100644 --- a/src/main/java/fr/xephi/authme/message/Messages.java +++ b/src/main/java/fr/xephi/authme/message/Messages.java @@ -107,7 +107,7 @@ public class Messages implements Reloadable { @Override public void reload() { this.messageFileHandler = messageFileHandlerProvider - .initializeHandler(lang -> "messages/messages_" + lang + ".yml"); + .initializeHandler(lang -> "messages/messages_" + lang + ".yml", "/authme messages"); } private static String formatMessage(String message) { diff --git a/src/main/resources/messages/help_br.yml b/src/main/resources/messages/help_br.yml index 1a143c8e..be4983ab 100644 --- a/src/main/resources/messages/help_br.yml +++ b/src/main/resources/messages/help_br.yml @@ -4,6 +4,7 @@ # ------------------------------------------------------- # Lista de textos usados na seção de ajuda: common: + header: '==========[ Ajuda AuthMeReloaded ]==========' optional: 'Opcional' hasPermission: 'Você tem permissão' noPermission: 'Sem Permissão' diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java index 292b0ec9..ea0f372a 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java @@ -39,10 +39,10 @@ public class HelpMessagesServiceTest { @Mock private MessageFileHandlerProvider messageFileHandlerProvider; - @SuppressWarnings("unchecked") @BeforeInjecting + @SuppressWarnings("unchecked") public void initializeHandler() { - MessageFileHandler handler = new MessageFileHandler(getJarFile(TEST_FILE), "messages/messages_en.yml"); + MessageFileHandler handler = new MessageFileHandler(getJarFile(TEST_FILE), "messages/messages_en.yml", null); given(messageFileHandlerProvider.initializeHandler(any(Function.class))).willReturn(handler); } diff --git a/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java new file mode 100644 index 00000000..399fd93a --- /dev/null +++ b/src/test/java/fr/xephi/authme/message/HelpMessageConsistencyTest.java @@ -0,0 +1,93 @@ +package fr.xephi.authme.message; + +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.command.help.HelpMessage; +import fr.xephi.authme.command.help.HelpSection; +import fr.xephi.authme.permission.DefaultPermission; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Tests that all help_xx.yml files contain all entries for + * {@link HelpSection}, {@link HelpMessage} and {@link DefaultPermission}. + */ +public class HelpMessageConsistencyTest { + + private static final String MESSAGES_FOLDER = "/messages"; + private static final Pattern HELP_MESSAGES_FILE = Pattern.compile("help_[a-z]+\\.yml"); + + private List helpFiles; + + @Before + public void findHelpMessagesFiles() { + File folder = TestHelper.getJarFile(MESSAGES_FOLDER); + File[] files = folder.listFiles(); + if (files == null || files.length == 0) { + throw new IllegalStateException("Could not get files from '" + MESSAGES_FOLDER + "'"); + } + helpFiles = Arrays.stream(files) + .filter(file -> HELP_MESSAGES_FILE.matcher(file.getName()).matches()) + .collect(Collectors.toList()); + } + + @Test + public void shouldHaveRequiredEntries() { + for (File file : helpFiles) { + // given + FileConfiguration configuration = YamlConfiguration.loadConfiguration(file); + + // when / then + assertHasAllHelpSectionEntries(file.getName(), configuration); + } + } + + private void assertHasAllHelpSectionEntries(String filename, FileConfiguration configuration) { + for (HelpSection section : HelpSection.values()) { + assertThat(filename + " should have entry for HelpSection '" + section + "'", + configuration.getString(section.getKey()), notEmptyString()); + } + + for (HelpMessage message : HelpMessage.values()) { + assertThat(filename + " should have entry for HelpMessage '" + message + "'", + configuration.getString(message.getKey()), notEmptyString()); + } + + for (DefaultPermission defaultPermission : DefaultPermission.values()) { + assertThat(filename + " should have entry for DefaultPermission '" + defaultPermission + "'", + configuration.getString(getPathForDefaultPermission(defaultPermission)), notEmptyString()); + } + } + + private static String getPathForDefaultPermission(DefaultPermission defaultPermission) { + String path = "common.defaultPermissions."; + switch (defaultPermission) { + case ALLOWED: + return path + "allowed"; + case NOT_ALLOWED: + return path + "notAllowed"; + case OP_ONLY: + return path + "opOnly"; + default: + throw new IllegalStateException("Unknown default permission '" + defaultPermission + "'"); + } + } + + private static Matcher notEmptyString() { + return both(not(emptyString())).and(not(nullValue())); + } +} diff --git a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java index 12949c5a..94fe9a0d 100644 --- a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java @@ -233,8 +233,8 @@ public class MessagesIntegrationTest { @SuppressWarnings("unchecked") private static MessageFileHandlerProvider providerReturning(File file, String defaultFile) { MessageFileHandlerProvider handler = mock(MessageFileHandlerProvider.class); - given(handler.initializeHandler(any(Function.class))) - .willReturn(new MessageFileHandler(file, defaultFile)); + given(handler.initializeHandler(any(Function.class), anyString())) + .willReturn(new MessageFileHandler(file, defaultFile, "/authme messages")); return handler; } } From 49f7e476457565bdff593011629b589314a84f31 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Feb 2017 13:09:38 +0100 Subject: [PATCH 17/79] Add more debug log flavors to ConsoleLogger --- .../java/fr/xephi/authme/ConsoleLogger.java | 94 +++++++++++++++++-- .../fr/xephi/authme/ConsoleLoggerTest.java | 36 ++++++- 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java index b0167459..9d18820c 100644 --- a/src/main/java/fr/xephi/authme/ConsoleLogger.java +++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java @@ -12,7 +12,10 @@ import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.function.Supplier; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -89,6 +92,18 @@ public final class ConsoleLogger { writeLog("[WARN] " + message); } + /** + * Log a Throwable with the provided message on WARNING level + * and save the stack trace to the log file. + * + * @param message The message to accompany the exception + * @param th The Throwable to log + */ + public static void logException(String message, Throwable th) { + warning(message + " " + StringUtils.formatException(th)); + writeLog(Throwables.getStackTraceAsString(th)); + } + /** * Log an INFO message. * @@ -114,6 +129,10 @@ public final class ConsoleLogger { } } + // -------- + // Debug log methods + // -------- + /** * Log a DEBUG message if enabled. *

@@ -124,21 +143,78 @@ public final class ConsoleLogger { */ public static void debug(String message) { if (logLevel.includes(LogLevel.DEBUG)) { - logger.info("Debug: " + message); - writeLog("[DEBUG] " + message); + String debugMessage = "[DEBUG] " + message; + logger.info(debugMessage); + writeLog(debugMessage); } } /** - * Log a Throwable with the provided message on WARNING level - * and save the stack trace to the log file. + * Log the DEBUG message from the supplier if enabled. * - * @param message The message to accompany the exception - * @param th The Throwable to log + * @param msgSupplier the message supplier */ - public static void logException(String message, Throwable th) { - warning(message + " " + StringUtils.formatException(th)); - writeLog(Throwables.getStackTraceAsString(th)); + public static void debug(Supplier msgSupplier) { + if (logLevel.includes(LogLevel.DEBUG)) { + String debugMessage = "[DEBUG] " + msgSupplier.get(); + logger.info(debugMessage); + writeLog(debugMessage); + } + } + + /** + * Log the DEBUG message. + * + * @param message the message + * @param param1 parameter to replace in the message + */ + public static void debug(String message, String param1) { + if (logLevel.includes(LogLevel.DEBUG)) { + String debugMessage = "[DEBUG] " + message; + logger.log(Level.INFO, debugMessage, param1); + writeLog(debugMessage + " {" + param1 + "}"); + } + } + + /** + * Log the DEBUG message. + * + * @param message the message + * @param param1 first param to replace in message + * @param param2 second param to replace in message + */ + // Avoids array creation if DEBUG level is disabled + public static void debug(String message, String param1, String param2) { + if (logLevel.includes(LogLevel.DEBUG)) { + debug(message, new String[]{param1, param2}); + } + } + + /** + * Log the DEBUG message. + * + * @param message the message + * @param params the params to replace in the message + */ + // Equivalent to debug(String, Object...) but avoids conversions + public static void debug(String message, String... params) { + if (logLevel.includes(LogLevel.DEBUG)) { + String debugMessage = "[DEBUG] " + message; + logger.log(Level.INFO, debugMessage, params); + writeLog(debugMessage + " {" + String.join(", ", params) + "}"); + } + } + + /** + * Log the DEBUG message. + * + * @param message the message + * @param params the params to replace in the message + */ + public static void debug(String message, Object... params) { + if (logLevel.includes(LogLevel.DEBUG)) { + debug(message, Arrays.stream(params).map(String::valueOf).toArray(String[]::new)); + } } diff --git a/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java index 8eae45ec..7d01a7db 100644 --- a/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java +++ b/src/test/java/fr/xephi/authme/ConsoleLoggerTest.java @@ -19,8 +19,10 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; @@ -42,9 +44,6 @@ public class ConsoleLoggerTest { @Mock private Logger logger; - @Mock - private Settings settings; - @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -106,7 +105,7 @@ public class ConsoleLoggerTest { ConsoleLogger.warning("Encountered a warning"); // then - verify(logger).info("Debug: Created test"); + verify(logger).info("[DEBUG] Created test"); verify(logger).warning("Encountered a warning"); verifyNoMoreInteractions(logger); assertThat(logFile.length(), equalTo(0L)); @@ -137,6 +136,35 @@ public class ConsoleLoggerTest { assertThat(String.join("", loggedLines), containsString(getClass().getCanonicalName())); } + @Test + public void shouldSupportVariousDebugMethods() throws IOException { + // given + ConsoleLogger.setLoggingOptions(newSettings(true, LogLevel.DEBUG)); + + // when + ConsoleLogger.debug("Got {0} entries", "test"); + ConsoleLogger.debug("Player `{0}` is in world `{1}`", "Bobby", "world"); + ConsoleLogger.debug("The {0} is {1} the {2}", "fox", "behind", "chicken"); + ConsoleLogger.debug("{0} quick {1} jump over {2} lazy {3} (reason: {4})", 5, "foxes", 3, "dogs", null); + ConsoleLogger.debug(() -> "Too little too late"); + + // then + verify(logger).log(Level.INFO, "[DEBUG] Got {0} entries", "test"); + verify(logger).log(Level.INFO, "[DEBUG] Player `{0}` is in world `{1}`", new Object[]{"Bobby", "world"}); + verify(logger).log(Level.INFO, "[DEBUG] The {0} is {1} the {2}", new Object[]{"fox", "behind", "chicken"}); + verify(logger).log(Level.INFO, "[DEBUG] {0} quick {1} jump over {2} lazy {3} (reason: {4})", + new Object[]{"5", "foxes", "3", "dogs", "null"}); + verify(logger).info("[DEBUG] Too little too late"); + + List loggedLines = Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8); + assertThat(loggedLines, contains( + containsString("[DEBUG] Got {0} entries {test}"), + containsString("[DEBUG] Player `{0}` is in world `{1}` {Bobby, world}"), + containsString("[DEBUG] The {0} is {1} the {2} {fox, behind, chicken}"), + containsString("[DEBUG] {0} quick {1} jump over {2} lazy {3} (reason: {4}) {5, foxes, 3, dogs, null}"), + containsString("[DEBUG] Too little too late"))); + } + @Test public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(ConsoleLogger.class); From 2b1a97e959921fc165971a22a677fbe3ec41dbdf Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Feb 2017 13:12:04 +0100 Subject: [PATCH 18/79] #761 Fix removal and restoration of primary permission group - Improve how a player is being switched between permission groups (add new group before removing old one) - Remove group handling logic from LimboCache: AuthGroupHandler is now solely responsible for changing the player's permission group --- .../xephi/authme/data/limbo/LimboCache.java | 22 +---- .../authme/permission/AuthGroupHandler.java | 93 ++++++++++++------- .../process/login/ProcessSyncPlayerLogin.java | 24 ++--- .../xephi/authme/service/CommonService.java | 5 +- .../authme/data/limbo/LimboCacheTest.java | 14 --- .../authme/service/CommonServiceTest.java | 4 +- 6 files changed, 78 insertions(+), 84 deletions(-) diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java index 622ed481..b6fa2944 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java @@ -1,10 +1,8 @@ package fr.xephi.authme.data.limbo; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; -import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.util.StringUtils; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -22,14 +20,11 @@ public class LimboCache { private final Map cache = new ConcurrentHashMap<>(); private LimboPlayerStorage limboPlayerStorage; - private Settings settings; private PermissionsManager permissionsManager; private SpawnLoader spawnLoader; @Inject - LimboCache(Settings settings, PermissionsManager permissionsManager, - SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) { - this.settings = settings; + LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) { this.permissionsManager = permissionsManager; this.spawnLoader = spawnLoader; this.limboPlayerStorage = limboPlayerStorage; @@ -51,6 +46,7 @@ public class LimboCache { if (permissionsManager.hasGroupSupport()) { playerGroup = permissionsManager.getPrimaryGroup(player); } + ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); if (limboPlayerStorage.hasData(player)) { LimboPlayer cache = limboPlayerStorage.readData(player); @@ -83,15 +79,14 @@ public class LimboCache { float walkSpeed = data.getWalkSpeed(); float flySpeed = data.getFlySpeed(); // Reset the speed value if it was 0 - if(walkSpeed < 0.01f) { + if (walkSpeed < 0.01f) { walkSpeed = 0.2f; } - if(flySpeed < 0.01f) { + if (flySpeed < 0.01f) { flySpeed = 0.2f; } player.setWalkSpeed(walkSpeed); player.setFlySpeed(flySpeed); - restoreGroup(player, data.getGroup()); data.clearTasks(); } } @@ -153,11 +148,4 @@ public class LimboCache { removeFromCache(player); addPlayerData(player); } - - private void restoreGroup(Player player, String group) { - if (!StringUtils.isEmpty(group) && permissionsManager.hasGroupSupport() - && settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { - permissionsManager.setGroup(player, group); - } - } } diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index 050b908a..5e14e986 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -13,6 +13,15 @@ import javax.inject.Inject; /** * Changes the permission group according to the auth status of the player and the configuration. + *

+ * If this feature is enabled, the primary permissions group of a player is replaced until he has + * logged in. Some permission plugins have a notion of a primary group; for other permission plugins the + * first group is simply taken. + *

+ * The groups that are used as replacement until the player logs in is configurable and depends on if + * the player is registered or not. Note that some (all?) permission systems require the group to actually + * exist for the replacement to take place. Furthermore, since some permission groups require that players + * be in at least one group, this will mean that the player is not removed from his primary group. */ public class AuthGroupHandler implements Reloadable { @@ -36,11 +45,49 @@ public class AuthGroupHandler implements Reloadable { * * @param player the player * @param groupType the group type - * - * @return True upon success, false otherwise. False is also returned if groups aren't supported - * with the current permissions system. */ - public boolean setGroup(Player player, AuthGroupType groupType) { + public void setGroup(Player player, AuthGroupType groupType) { + if (!useAuthGroups()) { + return; + } + + String primaryGroup = ""; + LimboPlayer limboPlayer = limboCache.getPlayerData(player.getName()); + if (limboPlayer != null) { + primaryGroup = limboPlayer.getGroup(); + } + + switch (groupType) { + // Implementation note: some permission systems don't support players not being in any group, + // so add the new group before removing the old ones + case UNREGISTERED: + permissionsManager.addGroup(player, unregisteredGroup); + permissionsManager.removeGroups(player, registeredGroup, primaryGroup); + break; + + case REGISTERED_UNAUTHENTICATED: + permissionsManager.addGroup(player, registeredGroup); + permissionsManager.removeGroups(player, unregisteredGroup, primaryGroup); + break; + + case LOGGED_IN: + restoreGroup(player); + break; + + default: + throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'"); + } + + ConsoleLogger.debug( + () -> player.getName() + " changed to " + groupType + ": has groups " + permissionsManager.getGroups(player)); + } + + /** + * Returns whether the auth permissions group function should be used. + * + * @return true if should be used, false otherwise + */ + private boolean useAuthGroups() { // Check whether the permissions check is enabled if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { return false; @@ -51,39 +98,21 @@ public class AuthGroupHandler implements Reloadable { ConsoleLogger.warning("The current permissions system doesn't have group support, unable to set group!"); return false; } - - switch (groupType) { - case UNREGISTERED: - // Remove the other group, set the current group - permissionsManager.removeGroups(player, registeredGroup); - return permissionsManager.addGroup(player, unregisteredGroup); - - case REGISTERED_UNAUTHENTICATED: - // Remove the other group, set the current group - permissionsManager.removeGroups(player, unregisteredGroup); - return permissionsManager.addGroup(player, registeredGroup); - - case LOGGED_IN: - return restoreGroup(player); - - default: - throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'"); - } + return true; } - private boolean restoreGroup(Player player) { - // Get the player's LimboPlayer + /** + * Restores the player's original primary group (taken from LimboPlayer). + * + * @param player the player to process + */ + private void restoreGroup(Player player) { LimboPlayer limbo = limboCache.getPlayerData(player.getName()); - if (limbo == null) { - return false; + if (limbo != null) { + String primaryGroup = limbo.getGroup(); + permissionsManager.addGroup(player, primaryGroup); } - - // Get the players group - String realGroup = limbo.getGroup(); - - // Remove the other group types groups, set the real group permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup); - return permissionsManager.addGroup(player, realGroup); } @Override 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 6fb5fa8b..70402e53 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -1,6 +1,5 @@ package fr.xephi.authme.process.login; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; @@ -8,17 +7,17 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.RestoreInventoryEvent; import fr.xephi.authme.listener.PlayerListener; +import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BungeeService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; -import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.WelcomeMessageConfiguration; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; -import org.bukkit.plugin.PluginManager; import org.bukkit.potion.PotionEffectType; import javax.inject.Inject; @@ -28,9 +27,6 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_IN public class ProcessSyncPlayerLogin implements SynchronousProcess { - @Inject - private AuthMe plugin; - @Inject private BungeeService bungeeService; @@ -40,9 +36,6 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private BukkitService bukkitService; - @Inject - private PluginManager pluginManager; - @Inject private TeleportationService teleportationService; @@ -53,7 +46,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { private CommandManager commandManager; @Inject - private Settings settings; + private CommonService commonService; @Inject private WelcomeMessageConfiguration welcomeMessageConfiguration; @@ -63,7 +56,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { private void restoreInventory(Player player) { RestoreInventoryEvent event = new RestoreInventoryEvent(player); - pluginManager.callEvent(event); + bukkitService.callEvent(event); if (!event.isCancelled()) { player.updateInventory(); } @@ -80,8 +73,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { // do we really need to use location from database for now? // because LimboCache#restoreData teleport player to last location. } + commonService.setGroup(player, AuthGroupType.LOGGED_IN); - if (settings.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { + if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { restoreInventory(player); } @@ -98,7 +92,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { } } - if (settings.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { + if (commonService.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { player.removePotionEffect(PotionEffectType.BLINDNESS); } @@ -108,8 +102,8 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { // Login is done, display welcome message List welcomeMessage = welcomeMessageConfiguration.getWelcomeMessage(player); - if (settings.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { - if (settings.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) { + if (commonService.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { + if (commonService.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) { welcomeMessage.forEach(bukkitService::broadcastMessage); } else { welcomeMessage.forEach(player::sendMessage); diff --git a/src/main/java/fr/xephi/authme/service/CommonService.java b/src/main/java/fr/xephi/authme/service/CommonService.java index c181f83b..cea51ea0 100644 --- a/src/main/java/fr/xephi/authme/service/CommonService.java +++ b/src/main/java/fr/xephi/authme/service/CommonService.java @@ -101,10 +101,9 @@ public class CommonService { * * @param player the player to process * @param group the group to add the player to - * @return true on success, false otherwise */ - public boolean setGroup(Player player, AuthGroupType group) { - return authGroupHandler.setGroup(player, group); + public void setGroup(Player player, AuthGroupType group) { + authGroupHandler.setGroup(player, group); } } diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java index fcc45451..152f2e35 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java @@ -2,9 +2,7 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; -import fr.xephi.authme.settings.properties.PluginSettings; import org.bukkit.Location; import org.bukkit.entity.Player; import org.junit.Test; @@ -33,9 +31,6 @@ public class LimboCacheTest { @InjectMocks private LimboCache limboCache; - @Mock - private Settings settings; - @Mock private PermissionsManager permissionsManager; @@ -118,11 +113,7 @@ public class LimboCacheTest { given(limboPlayer.isCanFly()).willReturn(true); float flySpeed = 1.0f; given(limboPlayer.getFlySpeed()).willReturn(flySpeed); - String group = "primary-group"; - given(limboPlayer.getGroup()).willReturn(group); getCache().put(name.toLowerCase(), limboPlayer); - given(settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)).willReturn(true); - given(permissionsManager.hasGroupSupport()).willReturn(true); // when limboCache.restoreData(player); @@ -132,7 +123,6 @@ public class LimboCacheTest { verify(player).setWalkSpeed(walkSpeed); verify(player).setAllowFlight(true); verify(player).setFlySpeed(flySpeed); - verify(permissionsManager).setGroup(player, group); verify(limboPlayer).clearTasks(); } @@ -147,11 +137,7 @@ public class LimboCacheTest { given(limboPlayer.getWalkSpeed()).willReturn(0f); given(limboPlayer.isCanFly()).willReturn(true); given(limboPlayer.getFlySpeed()).willReturn(0f); - String group = "primary-group"; - given(limboPlayer.getGroup()).willReturn(group); getCache().put(name.toLowerCase(), limboPlayer); - given(settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)).willReturn(true); - given(permissionsManager.hasGroupSupport()).willReturn(true); // when limboCache.restoreData(player); diff --git a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java index ff992bfe..7927f826 100644 --- a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java @@ -134,13 +134,11 @@ public class CommonServiceTest { // given Player player = mock(Player.class); AuthGroupType type = AuthGroupType.LOGGED_IN; - given(authGroupHandler.setGroup(player, type)).willReturn(true); // when - boolean result = commonService.setGroup(player, type); + commonService.setGroup(player, type); // then verify(authGroupHandler).setGroup(player, type); - assertThat(result, equalTo(true)); } } From 3eab42ae68662ff924c380c785074c800a471744 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Feb 2017 13:40:11 +0100 Subject: [PATCH 19/79] Remove obsolete "session expires on IP change" config - Session always expire on IP change; the config only controlled whether an error message was shown or not --- .../authme/process/join/AsynchronousJoin.java | 28 ++++++++++--------- .../settings/SettingsMigrationService.java | 3 +- .../settings/properties/PluginSettings.java | 11 +------- 3 files changed, 18 insertions(+), 24 deletions(-) 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 0f7e819f..3dcfe8e0 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -1,6 +1,5 @@ package fr.xephi.authme.process.join; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.SessionManager; import fr.xephi.authme.data.auth.PlayerAuth; @@ -8,30 +7,31 @@ import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.ProtectInventoryEvent; -import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.login.AsynchronousLogin; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.commandconfig.CommandManager; 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.task.LimboPlayerTaskManager; -import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.GameMode; +import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import javax.inject.Inject; -import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; +import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; /** * Asynchronous process for when a player joins. @@ -39,7 +39,7 @@ import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; public class AsynchronousJoin implements AsynchronousProcess { @Inject - private AuthMe plugin; + private Server server; @Inject private DataSource database; @@ -97,7 +97,7 @@ public class AsynchronousJoin implements AsynchronousProcess { public void run() { player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR)); if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) { - plugin.getServer().banIP(ip); + server.banIP(ip); } } }); @@ -130,12 +130,14 @@ public class AsynchronousJoin implements AsynchronousProcess { PlayerAuth auth = database.getAuth(name); database.setUnlogged(name); playerCache.removePlayer(name); - if (auth != null && auth.getIp().equals(ip)) { - service.send(player, MessageKey.SESSION_RECONNECTION); - bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player)); - return; - } else if (service.getProperty(PluginSettings.SESSIONS_EXPIRE_ON_IP_CHANGE)) { - service.send(player, MessageKey.SESSION_EXPIRED); + if (auth != null) { + if (auth.getIp().equals(ip)) { + service.send(player, MessageKey.SESSION_RECONNECTION); + bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player)); + return; + } else { + service.send(player, MessageKey.SESSION_EXPIRED); + } } } } else { diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java index e4b175b7..ade6833d 100644 --- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java +++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java @@ -82,7 +82,8 @@ public class SettingsMigrationService extends PlainMigrationService { "VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite", "Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping", "settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole", - "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole"}; + "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole", + "settings.sessions.sessionExpireOnIpChange"}; for (String deprecatedPath : deprecatedProperties) { if (resource.contains(deprecatedPath)) { return true; 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 eb6ffd6d..5f45ca5d 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java @@ -22,20 +22,11 @@ public class PluginSettings implements SettingsHolder { @Comment({ "After how many minutes should a session expire?", - "Remember that sessions will end only after the timeout, and", - "if the player's IP has changed but the timeout hasn't expired,", - "the player will be kicked from the server due to invalid session" + "A player's session ends after the timeout or if his IP has changed" }) public static final Property SESSIONS_TIMEOUT = newProperty("settings.sessions.timeout", 10); - @Comment({ - "Should the session expire if the player tries to log in with", - "another IP address?" - }) - public static final Property SESSIONS_EXPIRE_ON_IP_CHANGE = - newProperty("settings.sessions.sessionExpireOnIpChange", true); - @Comment({ "Message language, available languages:", "https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md" From 8ae06ed480878be2b9ed377d523d40a08020c6b0 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Feb 2017 13:48:24 +0100 Subject: [PATCH 20/79] Minor improvements to config layout - Change placement and comment of settings.restrictions.banUnsafedIP to make it clear that it only bans unknown IPs using a restricted username - Move "MySQL use SSL" option outside of the column options --- docs/config.md | 68 ++++++++----------- .../settings/properties/DatabaseSettings.java | 12 ++-- .../properties/RestrictionSettings.java | 10 +-- 3 files changed, 40 insertions(+), 50 deletions(-) diff --git a/docs/config.md b/docs/config.md index 73c5eed6..4b3396b5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ - + ## AuthMe Configuration The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder, @@ -18,9 +18,11 @@ DataSource: mySQLHost: '127.0.0.1' # Database port mySQLPort: '3306' - # Username about Database Connection Infos + # Connect to MySQL database over SSL + mySQLUseSSL: true + # Username to connect to the MySQL database mySQLUsername: 'authme' - # Password about Database Connection Infos + # Password to connect to the MySQL database mySQLPassword: '12345' # Database Name, use with converters or as SQLITE database name mySQLDatabase: 'authme' @@ -34,8 +36,6 @@ DataSource: mySQLRealName: 'realname' # Column for storing players passwords mySQLColumnPassword: 'password' - # Request mysql over SSL - mySQLUseSSL: true # Column for storing players emails mySQLColumnEmail: 'email' # Column for storing if a player is logged in or not @@ -94,13 +94,8 @@ settings: # expired, he will not need to authenticate. enabled: false # After how many minutes should a session expire? - # Remember that sessions will end only after the timeout, and - # if the player's IP has changed but the timeout hasn't expired, - # the player will be kicked from the server due to invalid session + # A player's session ends after the timeout or if his IP has changed timeout: 10 - # Should the session expire if the player tries to log in with - # another IP address? - sessionExpireOnIpChange: true # Message language, available languages: # https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md messagesLanguage: 'en' @@ -161,6 +156,8 @@ settings: # AllowedRestrictedUser: # - playername;127.0.0.1 AllowedRestrictedUser: [] + # Ban unknown IPs trying to log in with a restricted username? + banUnsafedIP: false # Should unregistered players be kicked immediately? kickNonRegistered: false # Should players be kicked on wrong password? @@ -177,7 +174,7 @@ settings: # After how many seconds should players who fail to login or register # be kicked? Set to 0 to disable. timeout: 30 - # Regex syntax of allowed characters in the player name. + # Regex pattern of allowed characters in the player name. allowedNicknameCharacters: '[a-zA-Z0-9_]*' # How far can unregistered players walk? # Set to 0 for unlimited radius @@ -189,8 +186,6 @@ settings: # Should we display all other accounts from a player when he joins? # permission: /authme.admin.accounts displayOtherAccounts: true - # Ban ip when the ip is not the ip registered in database - banUnsafedIP: false # Spawn priority; values: authme, essentials, multiverse, default spawnPriority: 'authme,essentials,multiverse,default' # Maximum Login authorized by IP @@ -223,18 +218,6 @@ settings: minPasswordLength: 5 # Maximum length of password passwordMaxLength: 30 - # This is a very important option: every time a player joins the server, - # if they are registered, AuthMe will switch him to unLoggedInGroup. - # This should prevent all major exploits. - # You can set up your permission plugin with this special group to have no permissions, - # or only permission to chat (or permission to send private messages etc.). - # The better way is to set up this group with few permissions, so if a player - # tries to exploit an account they can do only what you've defined for the group. - # After, a logged in player will be moved to his correct permissions group! - # Please note that the group name is case-sensitive, so 'admin' is different from 'Admin' - # Otherwise your group will be wiped and the player will join in the default group []! - # Example unLoggedinGroup: NotLogged - unLoggedinGroup: 'unLoggedinGroup' # Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL, # MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB, # PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at @@ -317,12 +300,24 @@ settings: # Do we need to prevent people to login with another case? # If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI preventOtherCase: true -permission: - # Take care with this option; if you want - # to use group switching of AuthMe - # for unloggedIn players, set this setting to true. - # Default is false. - EnablePermissionCheck: false +GroupOptions: + # Enables switching a player to defined permission groups before they log in. + # See below for a detailed explanation. + enablePermissionCheck: false + # This is a very important option: if a registered player joins the server + # AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits. + # You can set up your permission plugin with this special group to have no permissions, + # or only permission to chat (or permission to send private messages etc.). + # The better way is to set up this group with few permissions, so if a player + # tries to exploit an account they can do only what you've defined for the group. + # After login, the player will be moved to his correct permissions group! + # Please note that the group name is case-sensitive, so 'admin' is different from 'Admin' + # Otherwise your group will be wiped and the player will join in the default group []! + # Example: registeredPlayerGroup: 'NotLogged' + registeredPlayerGroup: '' + # Similar to above, unregistered players can be set to the following + # permissions group + unregisteredPlayerGroup: '' Email: # Email SMTP server host mailSMTP: 'smtp.gmail.com' @@ -366,18 +361,13 @@ Hooks: disableSocialSpy: false # Do we need to force /motd Essentials command on join? useEssentialsMotd: false -GroupOptions: - # Unregistered permission group - UnregisteredPlayerGroup: '' - # Registered permission group - RegisteredPlayerGroup: '' Protection: # Enable some servers protection (country based login, antibot) enableProtection: false # Apply the protection also to registered usernames enableProtectionRegistered: true # Countries allowed to join the server and register. For country codes, see - # http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ + # https://dev.bukkit.org/projects/authme-reloaded/pages/countries-codes # PLEASE USE QUOTES! countries: - 'US' @@ -464,4 +454,4 @@ To change settings on a running server, save your changes to config.yml and use --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Jan 14 22:12:16 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Feb 05 13:46:19 CET 2017 diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java index ed877f87..fde994af 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -26,11 +26,15 @@ public class DatabaseSettings implements SettingsHolder { public static final Property MYSQL_PORT = newProperty("DataSource.mySQLPort", "3306"); - @Comment("Username about Database Connection Infos") + @Comment("Connect to MySQL database over SSL") + public static final Property MYSQL_USE_SSL = + newProperty("DataSource.mySQLUseSSL", true); + + @Comment("Username to connect to the MySQL database") public static final Property MYSQL_USERNAME = newProperty("DataSource.mySQLUsername", "authme"); - @Comment("Password about Database Connection Infos") + @Comment("Password to connect to the MySQL database") public static final Property MYSQL_PASSWORD = newProperty("DataSource.mySQLPassword", "12345"); @@ -58,10 +62,6 @@ public class DatabaseSettings implements SettingsHolder { public static final Property MYSQL_COL_PASSWORD = newProperty("DataSource.mySQLColumnPassword", "password"); - @Comment("Request mysql over SSL") - public static final Property MYSQL_USE_SSL = - newProperty("DataSource.mySQLUseSSL", true); - @Comment("Column for storing players passwords salts") public static final Property MYSQL_COL_SALT = newProperty("ExternalBoardOptions.mySQLColumnSalt", ""); diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index 4e282728..d0677579 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -84,6 +84,10 @@ public class RestrictionSettings implements SettingsHolder { public static final Property> ALLOWED_RESTRICTED_USERS = newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); + @Comment("Ban unknown IPs trying to log in with a restricted username?") + public static final Property BAN_UNKNOWN_IP = + newProperty("settings.restrictions.banUnsafedIP", false); + @Comment("Should unregistered players be kicked immediately?") public static final Property KICK_NON_REGISTERED = newProperty("settings.restrictions.kickNonRegistered", false); @@ -115,7 +119,7 @@ public class RestrictionSettings implements SettingsHolder { public static final Property TIMEOUT = newProperty("settings.restrictions.timeout", 30); - @Comment("Regex syntax of allowed characters in the player name.") + @Comment("Regex pattern of allowed characters in the player name.") public static final Property ALLOWED_NICKNAME_CHARACTERS = newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*"); @@ -140,10 +144,6 @@ public class RestrictionSettings implements SettingsHolder { public static final Property DISPLAY_OTHER_ACCOUNTS = newProperty("settings.restrictions.displayOtherAccounts", true); - @Comment("Ban ip when the ip is not the ip registered in database") - public static final Property BAN_UNKNOWN_IP = - newProperty("settings.restrictions.banUnsafedIP", false); - @Comment("Spawn priority; values: authme, essentials, multiverse, default") public static final Property SPAWN_PRIORITY = newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default"); From d2fccdeb806a3ad094fe85288c604f1ddcbbdad8 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Feb 2017 16:52:35 +0100 Subject: [PATCH 21/79] Update Injector and create injectable object factory - Using e.g. Factory instead of the injector directly makes its purpose more specific and disallows any future abuse of the injector's functions --- pom.xml | 2 +- src/main/java/fr/xephi/authme/AuthMe.java | 6 ++- .../xephi/authme/command/CommandHandler.java | 14 +++--- .../executable/authme/ConverterCommand.java | 6 +-- .../initialization/factory/Factory.java | 19 ++++++++ .../factory/FactoryDependencyHandler.java | 46 +++++++++++++++++++ .../authme/security/PasswordSecurity.java | 6 +-- .../authme/AuthMeInitializationTest.java | 6 ++- .../authme/command/CommandHandlerTest.java | 7 +-- .../authme/ConverterCommandTest.java | 16 +++---- .../authme/security/PasswordSecurityTest.java | 5 +- .../tools/dependencygraph/DrawDependency.java | 4 +- src/test/java/tools/utils/InjectorUtils.java | 23 +--------- 13 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/initialization/factory/Factory.java create mode 100644 src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java diff --git a/pom.xml b/pom.xml index 622af6bd..d275b20d 100644 --- a/pom.xml +++ b/pom.xml @@ -433,7 +433,7 @@ ch.jalu injector - 0.3 + 0.4 compile true diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 1b987080..8ed9ffb3 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -14,6 +14,7 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver; import fr.xephi.authme.initialization.OnStartupTasks; import fr.xephi.authme.initialization.SettingsProvider; import fr.xephi.authme.initialization.TaskCloser; +import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.listener.EntityListener; import fr.xephi.authme.listener.PlayerListener; @@ -196,7 +197,10 @@ public class AuthMe extends JavaPlugin { getDataFolder().mkdir(); // Create injector, provide elements from the Bukkit environment and register providers - injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create(); + injector = new InjectorBuilder() + .addHandlers(new FactoryDependencyHandler()) + .addDefaultHandlers("fr.xephi.authme") + .create(); injector.register(AuthMe.class, this); injector.register(Server.class, getServer()); injector.register(PluginManager.class, getServer().getPluginManager()); diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java index 394b6f14..083d8a53 100644 --- a/src/main/java/fr/xephi/authme/command/CommandHandler.java +++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java @@ -1,8 +1,8 @@ package fr.xephi.authme.command; -import ch.jalu.injector.Injector; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.permission.PermissionsManager; @@ -40,13 +40,13 @@ public class CommandHandler { private Map, ExecutableCommand> commands = new HashMap<>(); @Inject - CommandHandler(Injector injector, CommandMapper commandMapper, PermissionsManager permissionsManager, - Messages messages, HelpProvider helpProvider) { + CommandHandler(Factory commandFactory, CommandMapper commandMapper, + PermissionsManager permissionsManager, Messages messages, HelpProvider helpProvider) { this.commandMapper = commandMapper; this.permissionsManager = permissionsManager; this.messages = messages; this.helpProvider = helpProvider; - initializeCommands(injector, commandMapper.getCommandClasses()); + initializeCommands(commandFactory, commandMapper.getCommandClasses()); } /** @@ -94,13 +94,13 @@ public class CommandHandler { /** * Initialize all required ExecutableCommand objects. * - * @param injector the injector + * @param commandFactory factory to create command objects * @param commandClasses the classes to instantiate */ - private void initializeCommands(Injector injector, + private void initializeCommands(Factory commandFactory, Set> commandClasses) { for (Class clazz : commandClasses) { - commands.put(clazz, injector.newInstance(clazz)); + commands.put(clazz, commandFactory.newInstance(clazz)); } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java index efa27ff0..e1c0661c 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import ch.jalu.injector.Injector; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import fr.xephi.authme.ConsoleLogger; @@ -13,6 +12,7 @@ import fr.xephi.authme.datasource.converter.RoyalAuthConverter; import fr.xephi.authme.datasource.converter.SqliteToSql; import fr.xephi.authme.datasource.converter.vAuthConverter; import fr.xephi.authme.datasource.converter.xAuthConverter; +import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; @@ -37,7 +37,7 @@ public class ConverterCommand implements ExecutableCommand { private BukkitService bukkitService; @Inject - private Injector injector; + private Factory converterFactory; @Override public void executeCommand(final CommandSender sender, List arguments) { @@ -52,7 +52,7 @@ public class ConverterCommand implements ExecutableCommand { } // Get the proper converter instance - final Converter converter = injector.newInstance(converterClass); + final Converter converter = converterFactory.newInstance(converterClass); // Run the convert job bukkitService.runTaskAsynchronously(new Runnable() { diff --git a/src/main/java/fr/xephi/authme/initialization/factory/Factory.java b/src/main/java/fr/xephi/authme/initialization/factory/Factory.java new file mode 100644 index 00000000..0f4ae62a --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/Factory.java @@ -0,0 +1,19 @@ +package fr.xephi.authme.initialization.factory; + +/** + * Injectable factory that creates new instances of a certain type. + * + * @param

the parent type to which the factory is limited to + */ +public interface Factory

{ + + /** + * Creates an instance of the given class. + * + * @param clazz the class to instantiate + * @param the class type + * @return new instance of the class + */ + C newInstance(Class clazz); + +} diff --git a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java new file mode 100644 index 00000000..04c11c68 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java @@ -0,0 +1,46 @@ +package fr.xephi.authme.initialization.factory; + +import ch.jalu.injector.Injector; +import ch.jalu.injector.context.ResolvedInstantiationContext; +import ch.jalu.injector.handlers.dependency.DependencyHandler; +import ch.jalu.injector.handlers.instantiation.DependencyDescription; +import ch.jalu.injector.utils.ReflectionUtils; + +/** + * Dependency handler that builds {@link Factory} objects. + */ +public class FactoryDependencyHandler implements DependencyHandler { + + @Override + public Object resolveValue(ResolvedInstantiationContext context, DependencyDescription dependencyDescription) { + if (dependencyDescription.getType() == Factory.class) { + Class genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType()); + if (genericType == null) { + throw new IllegalStateException("Factory fields must have concrete generic type. " + + "Cannot get generic type for field in '" + context.getMappedClass() + "'"); + } + + return new FactoryImpl<>(genericType, context.getInjector()); + } + return null; + } + + private static final class FactoryImpl

implements Factory

{ + + private final Injector injector; + private final Class

parentClass; + + FactoryImpl(Class

parentClass, Injector injector) { + this.parentClass = parentClass; + this.injector = injector; + } + + @Override + public C newInstance(Class clazz) { + if (parentClass.isAssignableFrom(clazz)) { + return injector.newInstance(clazz); + } + throw new IllegalArgumentException(clazz + " not child of " + parentClass); + } + } +} diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 8549b388..d2947108 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,9 +1,9 @@ package fr.xephi.authme.security; -import ch.jalu.injector.Injector; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.Settings; @@ -29,7 +29,7 @@ public class PasswordSecurity implements Reloadable { private PluginManager pluginManager; @Inject - private Injector injector; + private Factory hashAlgorithmFactory; private HashAlgorithm algorithm; private Collection legacyAlgorithms; @@ -154,7 +154,7 @@ public class PasswordSecurity implements Reloadable { if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) { return null; } - return injector.newInstance(algorithm.getClazz()); + return hashAlgorithmFactory.newInstance(algorithm.getClazz()); } private void hashPasswordForNewAlgorithm(String password, String playerName) { diff --git a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java index aae925b5..89132fcf 100644 --- a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java +++ b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java @@ -8,6 +8,7 @@ import fr.xephi.authme.api.NewAPI; import fr.xephi.authme.command.CommandHandler; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; @@ -91,7 +92,10 @@ public class AuthMeInitializationTest { Settings settings = new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData()); - Injector injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create(); + Injector injector = new InjectorBuilder() + .addHandlers(new FactoryDependencyHandler()) + .addDefaultHandlers("fr.xephi.authme") + .create(); injector.provide(DataFolder.class, dataFolder); injector.register(Server.class, server); injector.register(PluginManager.class, pluginManager); diff --git a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java index 1166cef2..e343199f 100644 --- a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java @@ -6,6 +6,7 @@ import fr.xephi.authme.command.TestCommandsUtil.TestLoginCommand; import fr.xephi.authme.command.TestCommandsUtil.TestRegisterCommand; import fr.xephi.authme.command.TestCommandsUtil.TestUnregisterCommand; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.permission.PermissionsManager; @@ -56,7 +57,7 @@ public class CommandHandlerTest { private CommandHandler handler; @Mock - private Injector injector; + private Factory commandFactory; @Mock private CommandMapper commandMapper; @Mock @@ -75,7 +76,7 @@ public class CommandHandlerTest { ExecutableCommand.class, TestLoginCommand.class, TestRegisterCommand.class, TestUnregisterCommand.class)); setInjectorToMockExecutableCommandClasses(); - handler = new CommandHandler(injector, commandMapper, permissionsManager, messages, helpProvider); + handler = new CommandHandler(commandFactory, commandMapper, permissionsManager, messages, helpProvider); } /** @@ -86,7 +87,7 @@ public class CommandHandlerTest { */ @SuppressWarnings("unchecked") private void setInjectorToMockExecutableCommandClasses() { - given(injector.newInstance(any(Class.class))).willAnswer(new Answer() { + given(commandFactory.newInstance(any(Class.class))).willAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Class clazz = invocation.getArgument(0); diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java index cf306599..0474eac1 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java @@ -1,8 +1,8 @@ package fr.xephi.authme.command.executable.authme; -import ch.jalu.injector.Injector; import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.converter.Converter; +import fr.xephi.authme.initialization.factory.Factory; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; @@ -48,7 +48,7 @@ public class ConverterCommandTest { private BukkitService bukkitService; @Mock - private Injector injector; + private Factory converterFactory; @BeforeClass public static void initLogger() { @@ -66,7 +66,7 @@ public class ConverterCommandTest { // then verify(sender).sendMessage(argThat(containsString("Converter does not exist"))); verifyNoMoreInteractions(commandService); - verifyZeroInteractions(injector); + verifyZeroInteractions(converterFactory); verifyZeroInteractions(bukkitService); } @@ -100,8 +100,8 @@ public class ConverterCommandTest { // then verify(converter).execute(sender); verifyNoMoreInteractions(converter); - verify(injector).newInstance(converterClass); - verifyNoMoreInteractions(injector); + verify(converterFactory).newInstance(converterClass); + verifyNoMoreInteractions(converterFactory); } @Test @@ -120,14 +120,14 @@ public class ConverterCommandTest { // then verify(converter).execute(sender); verifyNoMoreInteractions(converter); - verify(injector).newInstance(converterClass); - verifyNoMoreInteractions(injector); + verify(converterFactory).newInstance(converterClass); + verifyNoMoreInteractions(converterFactory); verify(commandService).send(sender, MessageKey.ERROR); } private T createMockReturnedByInjector(Class clazz) { T converter = mock(clazz); - given(injector.newInstance(clazz)).willReturn(converter); + given(converterFactory.newInstance(clazz)).willReturn(converter); return converter; } diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index e651ebea..04d2bc0b 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -6,6 +6,7 @@ import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.JOOMLA; @@ -84,7 +85,9 @@ public class PasswordSecurityTest { return null; } }).when(pluginManager).callEvent(any(Event.class)); - injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create(); + injector = new InjectorBuilder() + .addHandlers(new FactoryDependencyHandler()) + .addDefaultHandlers("fr.xephi.authme").create(); injector.register(Settings.class, settings); injector.register(DataSource.class, dataSource); injector.register(PluginManager.class, pluginManager); diff --git a/src/test/java/tools/dependencygraph/DrawDependency.java b/src/test/java/tools/dependencygraph/DrawDependency.java index 6b2c9c8a..34756ebb 100644 --- a/src/test/java/tools/dependencygraph/DrawDependency.java +++ b/src/test/java/tools/dependencygraph/DrawDependency.java @@ -2,6 +2,7 @@ package tools.dependencygraph; import ch.jalu.injector.handlers.instantiation.DependencyDescription; import ch.jalu.injector.handlers.instantiation.Instantiation; +import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; @@ -14,7 +15,6 @@ import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.security.crypts.EncryptionMethod; import org.bukkit.event.Listener; -import tools.utils.InjectorUtils; import tools.utils.ToolTask; import tools.utils.ToolsConstants; @@ -114,7 +114,7 @@ public class DrawDependency implements ToolTask { } private List getDependencies(Class clazz) { - Instantiation instantiation = InjectorUtils.getInstantiationMethod(clazz); + Instantiation instantiation = new StandardInjectionProvider().safeGet(clazz); return instantiation == null ? null : formatInjectionDependencies(instantiation); } diff --git a/src/test/java/tools/utils/InjectorUtils.java b/src/test/java/tools/utils/InjectorUtils.java index 0dbd1f4a..6b5d510e 100644 --- a/src/test/java/tools/utils/InjectorUtils.java +++ b/src/test/java/tools/utils/InjectorUtils.java @@ -1,12 +1,10 @@ package tools.utils; -import ch.jalu.injector.InjectorBuilder; import ch.jalu.injector.handlers.instantiation.DependencyDescription; import ch.jalu.injector.handlers.instantiation.Instantiation; -import ch.jalu.injector.handlers.instantiation.InstantiationProvider; +import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider; import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -24,7 +22,7 @@ public final class InjectorUtils { * @return the class' dependencies, or null if no instantiation method found */ public static Set> getDependencies(Class clazz) { - Instantiation instantiation = getInstantiationMethod(clazz); + Instantiation instantiation = new StandardInjectionProvider().safeGet(clazz); if (instantiation == null) { return null; } @@ -35,21 +33,4 @@ public final class InjectorUtils { return dependencies; } - /** - * Returns the instantiation method for the given class. - * - * @param clazz the class to process - * @return the instantiation method for the class, or null if none applicable - */ - public static Instantiation getInstantiationMethod(Class clazz) { - List providers = InjectorBuilder.createInstantiationProviders(); - for (InstantiationProvider provider : providers) { - Instantiation instantiation = provider.get(clazz); - if (instantiation != null) { - return instantiation; - } - } - return null; - } - } From 7c1a9062bae13c89867428c0c63fdad9a211e880 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 15 Feb 2017 20:05:14 +0100 Subject: [PATCH 22/79] #761 Simplify auth group handling --- .../authme/permission/AuthGroupHandler.java | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index 5e14e986..c7a3b343 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -10,6 +10,7 @@ import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; +import java.util.Optional; /** * Changes the permission group according to the auth status of the player and the configuration. @@ -51,11 +52,10 @@ public class AuthGroupHandler implements Reloadable { return; } - String primaryGroup = ""; - LimboPlayer limboPlayer = limboCache.getPlayerData(player.getName()); - if (limboPlayer != null) { - primaryGroup = limboPlayer.getGroup(); - } + String primaryGroup = Optional + .ofNullable(limboCache.getPlayerData(player.getName())) + .map(LimboPlayer::getGroup) + .orElse(""); switch (groupType) { // Implementation note: some permission systems don't support players not being in any group, @@ -71,7 +71,8 @@ public class AuthGroupHandler implements Reloadable { break; case LOGGED_IN: - restoreGroup(player); + permissionsManager.addGroup(player, primaryGroup); + permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup); break; default: @@ -101,20 +102,6 @@ public class AuthGroupHandler implements Reloadable { return true; } - /** - * Restores the player's original primary group (taken from LimboPlayer). - * - * @param player the player to process - */ - private void restoreGroup(Player player) { - LimboPlayer limbo = limboCache.getPlayerData(player.getName()); - if (limbo != null) { - String primaryGroup = limbo.getGroup(); - permissionsManager.addGroup(player, primaryGroup); - } - permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup); - } - @Override @PostConstruct public void reload() { From 8ac4ea05f69f69ef63eff0209ac2f53131b12cf3 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Wed, 15 Feb 2017 23:15:50 +0100 Subject: [PATCH 23/79] Update libs --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d275b20d..628bdb7d 100644 --- a/pom.xml +++ b/pom.xml @@ -495,7 +495,7 @@ com.zaxxer HikariCP - 2.5.1 + 2.6.0 compile @@ -509,7 +509,7 @@ org.slf4j slf4j-simple - 1.7.21 + 1.7.22 compile true From f0f2398e47e028b3906ecc0b6fa34c118ee46f40 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Wed, 15 Feb 2017 23:27:25 +0100 Subject: [PATCH 24/79] Incrase the auto poolSize value --- src/main/java/fr/xephi/authme/datasource/MySQL.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index f500ac07..f62b00e1 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -99,7 +99,7 @@ public class MySQL implements DataSource { this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); if (poolSize == -1) { - poolSize = Utils.getCoreCount(); + poolSize = Utils.getCoreCount()*3; } this.useSSL = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL); } From 2d3078daa4699fc5e88e44d62d747abfe75868e8 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 17 Feb 2017 19:25:04 +0100 Subject: [PATCH 25/79] Use the bStats maven artifact It seems to have issues accessing the maven repository, maybe it's just an issue with my local setup. (cherry picked from commit fc8c75c) --- pom.xml | 23 + .../authme/initialization/OnStartupTasks.java | 2 +- .../java/fr/xephi/authme/metrics/Metrics.java | 1031 ----------------- 3 files changed, 24 insertions(+), 1032 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/metrics/Metrics.java diff --git a/pom.xml b/pom.xml index 628bdb7d..ff67c0df 100644 --- a/pom.xml +++ b/pom.xml @@ -268,6 +268,11 @@ javax.inject fr.xephi.authme.libs.javax.inject + + + org.bstats + fr.xephi.authme.libs.org.bstats + target/${project.finalName}-spigot.jar @@ -318,6 +323,11 @@ javax.inject fr.xephi.authme.libs.javax.inject + + + org.bstats + fr.xephi.authme.libs.org.bstats + target/${project.finalName}-legacy.jar @@ -424,6 +434,12 @@ xephi-repo http://ci.xephi.fr/plugin/repository/everything/ + + + + bstats-repo + http://repo.bstats.org/content/groups/public + @@ -526,6 +542,13 @@ + + + org.bstats + bstats-bukkit + 1.0 + + com.comphenix.protocol diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index e939d11f..e803fafa 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -6,7 +6,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; -import fr.xephi.authme.metrics.Metrics; +import org.bstats.Metrics; import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.service.BukkitService; diff --git a/src/main/java/fr/xephi/authme/metrics/Metrics.java b/src/main/java/fr/xephi/authme/metrics/Metrics.java deleted file mode 100644 index 714036ce..00000000 --- a/src/main/java/fr/xephi/authme/metrics/Metrics.java +++ /dev/null @@ -1,1031 +0,0 @@ -package fr.xephi.authme.metrics; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.ServicePriority; -import org.bukkit.plugin.java.JavaPlugin; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import javax.net.ssl.HttpsURLConnection; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; - -/** - * bStats collects some data for plugin authors. - *

- * Check out https://bStats.org/ to learn more about bStats! - */ -public class Metrics { - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData"; - - // Should failed requests be logged? - private static boolean logFailedRequests; - - // The uuid of the server - private static String serverUUID; - - // The plugin - private final JavaPlugin plugin; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - */ - public Metrics(JavaPlugin plugin) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - - // Inform the server owners about bStats - config.options().header( - "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + - "To honor their work, you should not disable it.\n" + - "This has nearly no effect on the server performance!\n" + - "Check out https://bStats.org/ to learn more :)" - ).copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - } - } - - // Load the data - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - if (config.getBoolean("enabled", true)) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException ignored) { - } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, new Runnable() { - @Override - public void run() { - submitData(); - } - }); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - public JSONObject getPluginData() { - JSONObject data = new JSONObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.put("pluginName", pluginName); // Append the name of the plugin - data.put("pluginVersion", pluginVersion); // Append the version of the plugin - JSONArray customCharts = new JSONArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JSONObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.put("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private JSONObject getServerData() { - // Minecraft specific data - int playerAmount = Bukkit.getOnlinePlayers().size(); - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = org.bukkit.Bukkit.getVersion(); - bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JSONObject data = new JSONObject(); - - data.put("serverUUID", serverUUID); - - data.put("playerAmount", playerAmount); - data.put("onlineMode", onlineMode); - data.put("bukkitVersion", bukkitVersion); - - data.put("javaVersion", javaVersion); - data.put("osName", osName); - data.put("osArch", osArch); - data.put("osVersion", osVersion); - data.put("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - private void submitData() { - final JSONObject data = getServerData(); - - JSONArray pluginData = new JSONArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - } catch (NoSuchFieldException ignored) { - continue; // Continue "searching" - } - // Found one! - try { - pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service))); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { - } - } - - data.put("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(new Runnable() { - @Override - public void run() { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - } - }).start(); - - } - - /** - * Sends the data to the bStats server. - * - * @param data The data to send. - * - * @throws Exception If the request failed. - */ - private static void sendData(JSONObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); - outputStream.write(compressedData); - outputStream.flush(); - outputStream.close(); - - connection.getInputStream().close(); // We don't care about the response - Just send our data :) - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * - * @return The gzipped String. - * - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes("UTF-8")); - gzip.close(); - return outputStream.toByteArray(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - protected final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - protected JSONObject getRequestJsonObject() { - JSONObject chart = new JSONObject(); - chart.put("chartId", chartId); - try { - JSONObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.put("data", data); - } catch (Throwable t) { - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - protected abstract JSONObject getChartData(); - - } - - /** - * Represents a custom simple pie. - */ - public static abstract class SimplePie extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimplePie(String chartId) { - super(chartId); - } - - /** - * Gets the value of the pie. - * - * @return The value of the pie. - */ - public abstract String getValue(); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - String value = getValue(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static abstract class AdvancedPie extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedPie(String chartId) { - super(chartId); - } - - /** - * Gets the values of the pie. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * - * @return The values of the pie. - */ - public abstract HashMap getValues(HashMap valueMap); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap map = getValues(new HashMap()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static abstract class SingleLineChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SingleLineChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @return The value of the chart. - */ - public abstract int getValue(); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - int value = getValue(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.put("value", value); - return data; - } - - } - - /** - * Represents a custom multi line chart. - */ - public static abstract class MultiLineChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public MultiLineChart(String chartId) { - super(chartId); - } - - /** - * Gets the values of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * - * @return The values of the chart. - */ - public abstract HashMap getValues(HashMap valueMap); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap map = getValues(new HashMap()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom simple bar chart. - */ - public static abstract class SimpleBarChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimpleBarChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * - * @return The value of the chart. - */ - public abstract HashMap getValues(HashMap valueMap); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap map = getValues(new HashMap()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - JSONArray categoryValues = new JSONArray(); - categoryValues.add(entry.getValue()); - values.put(entry.getKey(), categoryValues); - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom advanced bar chart. - */ - public static abstract class AdvancedBarChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedBarChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * - * @return The value of the chart. - */ - public abstract HashMap getValues(HashMap valueMap); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap map = getValues(new HashMap()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - continue; // Skip this invalid - } - allSkipped = false; - JSONArray categoryValues = new JSONArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(categoryValue); - } - values.put(entry.getKey(), categoryValues); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * Represents a custom simple map chart. - */ - public static abstract class SimpleMapChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public SimpleMapChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @return The value of the chart. - */ - public abstract Country getValue(); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - Country value = getValue(); - - if (value == null) { - // Null = skip the chart - return null; - } - data.put("value", value.getCountryIsoTag()); - return data; - } - - } - - /** - * Represents a custom advanced map chart. - */ - public static abstract class AdvancedMapChart extends CustomChart { - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - public AdvancedMapChart(String chartId) { - super(chartId); - } - - /** - * Gets the value of the chart. - * - * @param valueMap Just an empty map. The only reason it exists is to make your life easier. - * You don't have to create a map yourself! - * - * @return The value of the chart. - */ - public abstract HashMap getValues(HashMap valueMap); - - @Override - protected JSONObject getChartData() { - JSONObject data = new JSONObject(); - JSONObject values = new JSONObject(); - HashMap map = getValues(new HashMap()); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.put(entry.getKey().getCountryIsoTag(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.put("values", values); - return data; - } - - } - - /** - * A enum which is used for custom maps. - */ - public enum Country { - - /** - * bStats will use the country of the server. - */ - AUTO_DETECT("AUTO", "Auto Detected"), - - ANDORRA("AD", "Andorra"), - UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"), - AFGHANISTAN("AF", "Afghanistan"), - ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"), - ANGUILLA("AI", "Anguilla"), - ALBANIA("AL", "Albania"), - ARMENIA("AM", "Armenia"), - NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"), - ANGOLA("AO", "Angola"), - ANTARCTICA("AQ", "Antarctica"), - ARGENTINA("AR", "Argentina"), - AMERICAN_SAMOA("AS", "American Samoa"), - AUSTRIA("AT", "Austria"), - AUSTRALIA("AU", "Australia"), - ARUBA("AW", "Aruba"), - ÅLAND_ISLANDS("AX", "Åland Islands"), - AZERBAIJAN("AZ", "Azerbaijan"), - BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"), - BARBADOS("BB", "Barbados"), - BANGLADESH("BD", "Bangladesh"), - BELGIUM("BE", "Belgium"), - BURKINA_FASO("BF", "Burkina Faso"), - BULGARIA("BG", "Bulgaria"), - BAHRAIN("BH", "Bahrain"), - BURUNDI("BI", "Burundi"), - BENIN("BJ", "Benin"), - SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"), - BERMUDA("BM", "Bermuda"), - BRUNEI("BN", "Brunei"), - BOLIVIA("BO", "Bolivia"), - BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"), - BRAZIL("BR", "Brazil"), - BAHAMAS("BS", "Bahamas"), - BHUTAN("BT", "Bhutan"), - BOUVET_ISLAND("BV", "Bouvet Island"), - BOTSWANA("BW", "Botswana"), - BELARUS("BY", "Belarus"), - BELIZE("BZ", "Belize"), - CANADA("CA", "Canada"), - COCOS_ISLANDS("CC", "Cocos Islands"), - THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"), - CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"), - CONGO("CG", "Congo"), - SWITZERLAND("CH", "Switzerland"), - CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"), - COOK_ISLANDS("CK", "Cook Islands"), - CHILE("CL", "Chile"), - CAMEROON("CM", "Cameroon"), - CHINA("CN", "China"), - COLOMBIA("CO", "Colombia"), - COSTA_RICA("CR", "Costa Rica"), - CUBA("CU", "Cuba"), - CAPE_VERDE("CV", "Cape Verde"), - CURAÇAO("CW", "Curaçao"), - CHRISTMAS_ISLAND("CX", "Christmas Island"), - CYPRUS("CY", "Cyprus"), - CZECH_REPUBLIC("CZ", "Czech Republic"), - GERMANY("DE", "Germany"), - DJIBOUTI("DJ", "Djibouti"), - DENMARK("DK", "Denmark"), - DOMINICA("DM", "Dominica"), - DOMINICAN_REPUBLIC("DO", "Dominican Republic"), - ALGERIA("DZ", "Algeria"), - ECUADOR("EC", "Ecuador"), - ESTONIA("EE", "Estonia"), - EGYPT("EG", "Egypt"), - WESTERN_SAHARA("EH", "Western Sahara"), - ERITREA("ER", "Eritrea"), - SPAIN("ES", "Spain"), - ETHIOPIA("ET", "Ethiopia"), - FINLAND("FI", "Finland"), - FIJI("FJ", "Fiji"), - FALKLAND_ISLANDS("FK", "Falkland Islands"), - MICRONESIA("FM", "Micronesia"), - FAROE_ISLANDS("FO", "Faroe Islands"), - FRANCE("FR", "France"), - GABON("GA", "Gabon"), - UNITED_KINGDOM("GB", "United Kingdom"), - GRENADA("GD", "Grenada"), - GEORGIA("GE", "Georgia"), - FRENCH_GUIANA("GF", "French Guiana"), - GUERNSEY("GG", "Guernsey"), - GHANA("GH", "Ghana"), - GIBRALTAR("GI", "Gibraltar"), - GREENLAND("GL", "Greenland"), - GAMBIA("GM", "Gambia"), - GUINEA("GN", "Guinea"), - GUADELOUPE("GP", "Guadeloupe"), - EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"), - GREECE("GR", "Greece"), - SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"), - GUATEMALA("GT", "Guatemala"), - GUAM("GU", "Guam"), - GUINEA_BISSAU("GW", "Guinea-Bissau"), - GUYANA("GY", "Guyana"), - HONG_KONG("HK", "Hong Kong"), - HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"), - HONDURAS("HN", "Honduras"), - CROATIA("HR", "Croatia"), - HAITI("HT", "Haiti"), - HUNGARY("HU", "Hungary"), - INDONESIA("ID", "Indonesia"), - IRELAND("IE", "Ireland"), - ISRAEL("IL", "Israel"), - ISLE_OF_MAN("IM", "Isle Of Man"), - INDIA("IN", "India"), - BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"), - IRAQ("IQ", "Iraq"), - IRAN("IR", "Iran"), - ICELAND("IS", "Iceland"), - ITALY("IT", "Italy"), - JERSEY("JE", "Jersey"), - JAMAICA("JM", "Jamaica"), - JORDAN("JO", "Jordan"), - JAPAN("JP", "Japan"), - KENYA("KE", "Kenya"), - KYRGYZSTAN("KG", "Kyrgyzstan"), - CAMBODIA("KH", "Cambodia"), - KIRIBATI("KI", "Kiribati"), - COMOROS("KM", "Comoros"), - SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"), - NORTH_KOREA("KP", "North Korea"), - SOUTH_KOREA("KR", "South Korea"), - KUWAIT("KW", "Kuwait"), - CAYMAN_ISLANDS("KY", "Cayman Islands"), - KAZAKHSTAN("KZ", "Kazakhstan"), - LAOS("LA", "Laos"), - LEBANON("LB", "Lebanon"), - SAINT_LUCIA("LC", "Saint Lucia"), - LIECHTENSTEIN("LI", "Liechtenstein"), - SRI_LANKA("LK", "Sri Lanka"), - LIBERIA("LR", "Liberia"), - LESOTHO("LS", "Lesotho"), - LITHUANIA("LT", "Lithuania"), - LUXEMBOURG("LU", "Luxembourg"), - LATVIA("LV", "Latvia"), - LIBYA("LY", "Libya"), - MOROCCO("MA", "Morocco"), - MONACO("MC", "Monaco"), - MOLDOVA("MD", "Moldova"), - MONTENEGRO("ME", "Montenegro"), - SAINT_MARTIN("MF", "Saint Martin"), - MADAGASCAR("MG", "Madagascar"), - MARSHALL_ISLANDS("MH", "Marshall Islands"), - MACEDONIA("MK", "Macedonia"), - MALI("ML", "Mali"), - MYANMAR("MM", "Myanmar"), - MONGOLIA("MN", "Mongolia"), - MACAO("MO", "Macao"), - NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"), - MARTINIQUE("MQ", "Martinique"), - MAURITANIA("MR", "Mauritania"), - MONTSERRAT("MS", "Montserrat"), - MALTA("MT", "Malta"), - MAURITIUS("MU", "Mauritius"), - MALDIVES("MV", "Maldives"), - MALAWI("MW", "Malawi"), - MEXICO("MX", "Mexico"), - MALAYSIA("MY", "Malaysia"), - MOZAMBIQUE("MZ", "Mozambique"), - NAMIBIA("NA", "Namibia"), - NEW_CALEDONIA("NC", "New Caledonia"), - NIGER("NE", "Niger"), - NORFOLK_ISLAND("NF", "Norfolk Island"), - NIGERIA("NG", "Nigeria"), - NICARAGUA("NI", "Nicaragua"), - NETHERLANDS("NL", "Netherlands"), - NORWAY("NO", "Norway"), - NEPAL("NP", "Nepal"), - NAURU("NR", "Nauru"), - NIUE("NU", "Niue"), - NEW_ZEALAND("NZ", "New Zealand"), - OMAN("OM", "Oman"), - PANAMA("PA", "Panama"), - PERU("PE", "Peru"), - FRENCH_POLYNESIA("PF", "French Polynesia"), - PAPUA_NEW_GUINEA("PG", "Papua New Guinea"), - PHILIPPINES("PH", "Philippines"), - PAKISTAN("PK", "Pakistan"), - POLAND("PL", "Poland"), - SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"), - PITCAIRN("PN", "Pitcairn"), - PUERTO_RICO("PR", "Puerto Rico"), - PALESTINE("PS", "Palestine"), - PORTUGAL("PT", "Portugal"), - PALAU("PW", "Palau"), - PARAGUAY("PY", "Paraguay"), - QATAR("QA", "Qatar"), - REUNION("RE", "Reunion"), - ROMANIA("RO", "Romania"), - SERBIA("RS", "Serbia"), - RUSSIA("RU", "Russia"), - RWANDA("RW", "Rwanda"), - SAUDI_ARABIA("SA", "Saudi Arabia"), - SOLOMON_ISLANDS("SB", "Solomon Islands"), - SEYCHELLES("SC", "Seychelles"), - SUDAN("SD", "Sudan"), - SWEDEN("SE", "Sweden"), - SINGAPORE("SG", "Singapore"), - SAINT_HELENA("SH", "Saint Helena"), - SLOVENIA("SI", "Slovenia"), - SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"), - SLOVAKIA("SK", "Slovakia"), - SIERRA_LEONE("SL", "Sierra Leone"), - SAN_MARINO("SM", "San Marino"), - SENEGAL("SN", "Senegal"), - SOMALIA("SO", "Somalia"), - SURINAME("SR", "Suriname"), - SOUTH_SUDAN("SS", "South Sudan"), - SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"), - EL_SALVADOR("SV", "El Salvador"), - SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"), - SYRIA("SY", "Syria"), - SWAZILAND("SZ", "Swaziland"), - TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"), - CHAD("TD", "Chad"), - FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"), - TOGO("TG", "Togo"), - THAILAND("TH", "Thailand"), - TAJIKISTAN("TJ", "Tajikistan"), - TOKELAU("TK", "Tokelau"), - TIMOR_LESTE("TL", "Timor-Leste"), - TURKMENISTAN("TM", "Turkmenistan"), - TUNISIA("TN", "Tunisia"), - TONGA("TO", "Tonga"), - TURKEY("TR", "Turkey"), - TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"), - TUVALU("TV", "Tuvalu"), - TAIWAN("TW", "Taiwan"), - TANZANIA("TZ", "Tanzania"), - UKRAINE("UA", "Ukraine"), - UGANDA("UG", "Uganda"), - UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"), - UNITED_STATES("US", "United States"), - URUGUAY("UY", "Uruguay"), - UZBEKISTAN("UZ", "Uzbekistan"), - VATICAN("VA", "Vatican"), - SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"), - VENEZUELA("VE", "Venezuela"), - BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"), - U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"), - VIETNAM("VN", "Vietnam"), - VANUATU("VU", "Vanuatu"), - WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"), - SAMOA("WS", "Samoa"), - YEMEN("YE", "Yemen"), - MAYOTTE("YT", "Mayotte"), - SOUTH_AFRICA("ZA", "South Africa"), - ZAMBIA("ZM", "Zambia"), - ZIMBABWE("ZW", "Zimbabwe"); - - private String isoTag; - private String name; - - Country(String isoTag, String name) { - this.isoTag = isoTag; - this.name = name; - } - - /** - * Gets the name of the country. - * - * @return The name of the country. - */ - public String getCountryName() { - return name; - } - - /** - * Gets the iso tag of the country. - * - * @return The iso tag of the country. - */ - public String getCountryIsoTag() { - return isoTag; - } - - /** - * Gets a country by it's iso tag. - * - * @param isoTag The iso tag of the county. - * - * @return The country with the given iso tag or null if unknown. - */ - public static Country byIsoTag(String isoTag) { - for (Country country : Country.values()) { - if (country.getCountryIsoTag().equals(isoTag)) { - return country; - } - } - return null; - } - - /** - * Gets a country by a locale. - * - * @param locale The locale. - * - * @return The country from the giben locale or null if unknown country or - * if the locale does not contain a country. - */ - public static Country byLocale(Locale locale) { - return byIsoTag(locale.getCountry()); - } - } -} From e3426cd7318718872cbeac127abc18169cb12081 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 15:13:26 +0100 Subject: [PATCH 26/79] Display hint when legacy jar should be used (cf. #1099) --- src/main/java/fr/xephi/authme/AuthMe.java | 1 + .../authme/initialization/OnStartupTasks.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 8ed9ffb3..128e9693 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -135,6 +135,7 @@ public class AuthMe extends JavaPlugin { initialize(); } catch (Exception e) { ConsoleLogger.logException("Aborting initialization of AuthMe:", e); + OnStartupTasks.displayLegacyJarHint(e); stopOrUnload(); return; } diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index e803fafa..34996508 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -1,5 +1,6 @@ package fr.xephi.authme.initialization; +import ch.jalu.injector.exceptions.InjectorReflectionException; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; @@ -21,6 +22,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import javax.inject.Inject; +import java.util.Optional; import java.util.logging.Logger; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; @@ -111,4 +113,23 @@ public class OnStartupTasks { } }, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL)); } + + /** + * Displays a hint to use the legacy AuthMe JAR if AuthMe could not be started + * because Gson was not found. + * + * @param e the exception to process + */ + public static void displayLegacyJarHint(Exception e) { + if (e instanceof InjectorReflectionException) { + Throwable causeOfCause = Optional.of(e) + .map(Throwable::getCause) + .map(Throwable::getCause).orElse(null); + if (causeOfCause instanceof NoClassDefFoundError + && "Lcom/google/gson/Gson;".equals(causeOfCause.getMessage())) { + ConsoleLogger.warning("YOU MUST DOWNLOAD THE LEGACY JAR TO USE AUTHME ON YOUR SERVER"); + ConsoleLogger.warning("Get authme-legacy.jar from http://ci.xephi.fr/job/AuthMeReloaded/"); + } + } + } } From c9b66183ded2b1d22e0e1affe1b2b1f001453a59 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 15:56:08 +0100 Subject: [PATCH 27/79] Fix command mapping for /authme:unregister etc. --- .../fr/xephi/authme/command/CommandMapper.java | 3 +++ .../xephi/authme/command/CommandMapperTest.java | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java index c5cf91c0..33003c3a 100644 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -123,6 +123,9 @@ public class CommandMapper { private CommandDescription getBaseCommand(String label) { String baseLabel = label.toLowerCase(); + if (baseLabel.startsWith("authme:")) { + baseLabel = baseLabel.substring("authme:".length()); + } for (CommandDescription command : baseCommands) { if (command.hasLabel(baseLabel)) { return command; diff --git a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java index dd7bad6e..3a23cdc8 100644 --- a/src/test/java/fr/xephi/authme/command/CommandMapperTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java @@ -290,6 +290,21 @@ public class CommandMapperTest { assertThat(result.getArguments(), contains(parts.get(2))); } + @Test + public void shouldSupportAuthMePrefix() { + // given + List parts = asList("authme:unregister", "Betty"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(true); + + // when + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); + + // then + assertThat(result.getResultStatus(), equalTo(FoundResultStatus.SUCCESS)); + assertThat(result.getCommandDescription(), equalTo(getCommandWithLabel(commands, "unregister"))); + } + @SuppressWarnings("unchecked") @Test public void shouldReturnExecutableCommandClasses() { From 6937dd37fb50306d9878a97a5ec3d25e7c400e61 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 17:53:34 +0100 Subject: [PATCH 28/79] #1034 Create subcommand to send test email - Add test email feature - Change debug command to lazily instantiate its subcommands --- .../executable/authme/debug/DebugCommand.java | 31 +++++--- .../authme/debug/TestEmailSender.java | 78 +++++++++++++++++++ .../fr/xephi/authme/mail/SendMailSSL.java | 15 ++++ 3 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index f9541deb..8e7168f2 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -1,15 +1,16 @@ package fr.xephi.authme.command.executable.authme.debug; +import com.google.common.collect.ImmutableSet; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.initialization.factory.Factory; import org.bukkit.command.CommandSender; -import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Debug command main. @@ -17,25 +18,21 @@ import java.util.stream.Stream; public class DebugCommand implements ExecutableCommand { @Inject - private PermissionGroups permissionGroups; + private Factory debugSectionFactory; + + private Set> sectionClasses = + ImmutableSet.of(PermissionGroups.class, TestEmailSender.class); private Map sections; - @PostConstruct - private void collectSections() { - Map sections = Stream.of(permissionGroups) - .collect(Collectors.toMap(DebugSection::getName, Function.identity())); - this.sections = sections; - } - @Override public void executeCommand(CommandSender sender, List arguments) { if (arguments.isEmpty()) { sender.sendMessage("Available sections:"); - sections.values() + getSections().values() .forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription())); } else { - DebugSection debugSection = sections.get(arguments.get(0).toLowerCase()); + DebugSection debugSection = getSections().get(arguments.get(0).toLowerCase()); if (debugSection == null) { sender.sendMessage("Unknown subcommand"); } else { @@ -43,4 +40,14 @@ public class DebugCommand implements ExecutableCommand { } } } + + // Lazy getter + private Map getSections() { + if (sections == null) { + sections = sectionClasses.stream() + .map(debugSectionFactory::newInstance) + .collect(Collectors.toMap(DebugSection::getName, Function.identity())); + } + return sections; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java new file mode 100644 index 00000000..cfad4013 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -0,0 +1,78 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.SendMailSSL; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; + +/** + * Sends out a test email. + */ +class TestEmailSender implements DebugSection { + + @Inject + private DataSource dataSource; + + @Inject + private SendMailSSL sendMailSSL; + + + @Override + public String getName() { + return "mail"; + } + + @Override + public String getDescription() { + return "Sends out a test email"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (!sendMailSSL.hasAllInformation()) { + sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + + "for sending emails. Please check your config.yml"); + return; + } + + String email = getEmail(sender, arguments); + + // getEmail() takes care of informing the sender of the error if email == null + if (email != null) { + boolean sendMail = sendMailSSL.sendTestEmail(email); + if (sendMail) { + sender.sendMessage("Test email sent to " + email + " with success"); + } else { + sender.sendMessage(ChatColor.RED + "Failed to send test mail to " + email + "; please check your logs"); + } + } + } + + private String getEmail(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + PlayerAuth auth = dataSource.getAuth(sender.getName()); + if (auth == null) { + sender.sendMessage(ChatColor.RED + "Please provide an email address, " + + "e.g. /authme debug mail test@example.com"); + return null; + } + String email = auth.getEmail(); + if (email == null || "your@email.com".equals(email)) { + sender.sendMessage(ChatColor.RED + "No email set for your account! Please use /authme debug mail "); + return null; + } + return email; + } else { + String email = arguments.get(0); + if (email.contains("@")) { + return email; + } + sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com"); + return null; + } + } +} diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index a782d3a1..f5952e40 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -109,6 +109,21 @@ public class SendMailSSL { return sendEmail(message, htmlEmail); } + public boolean sendTestEmail(String email) { + HtmlEmail htmlEmail; + try { + htmlEmail = initializeMail(email); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email for sample email:", e); + return false; + } + + htmlEmail.setSubject("AuthMe test email"); + String message = "Hello there!
This is a sample email sent to you from a Minecraft server (" + + serverName + ") via /authme debug mail. If you're seeing this, sending emails should be fine."; + return sendEmail(message, htmlEmail); + } + private File generateImage(String name, String newPass) throws IOException { ImageGenerator gen = new ImageGenerator(newPass); File file = new File(dataFolder, name + "_new_pass.jpg"); From ef1d006cdfa01eee9c3c835247598976d89be06a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 21:31:37 +0100 Subject: [PATCH 29/79] #949 Create expiring map type + integrate it into recovery code service --- .../authme/service/RecoveryCodeService.java | 55 +++------ .../fr/xephi/authme/util/ExpiringMap.java | 114 ++++++++++++++++++ .../service/RecoveryCodeServiceTest.java | 29 +---- .../fr/xephi/authme/util/ExpiringMapTest.java | 94 +++++++++++++++ 4 files changed, 231 insertions(+), 61 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/util/ExpiringMap.java create mode 100644 src/test/java/fr/xephi/authme/util/ExpiringMapTest.java diff --git a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java index e7fa37ad..fdc987a2 100644 --- a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java +++ b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java @@ -1,38 +1,38 @@ package fr.xephi.authme.service; -import com.google.common.annotations.VisibleForTesting; +import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.ExpiringMap; +import fr.xephi.authme.util.RandomStringUtils; import javax.inject.Inject; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID; -import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR; /** * Manager for recovery codes. */ -public class RecoveryCodeService implements SettingsDependent { - - private Map recoveryCodes = new ConcurrentHashMap<>(); +public class RecoveryCodeService implements SettingsDependent, HasCleanup { + private final ExpiringMap recoveryCodes; private int recoveryCodeLength; - private long recoveryCodeExpirationMillis; + private int recoveryCodeExpiration; @Inject RecoveryCodeService(Settings settings) { - reload(settings); + recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH); + recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID); + recoveryCodes = new ExpiringMap<>(recoveryCodeExpiration, TimeUnit.HOURS); } /** * @return whether recovery codes are enabled or not */ public boolean isRecoveryCodeNeeded() { - return recoveryCodeLength > 0 && recoveryCodeExpirationMillis > 0; + return recoveryCodeLength > 0 && recoveryCodeExpiration > 0; } /** @@ -43,7 +43,7 @@ public class RecoveryCodeService implements SettingsDependent { */ public String generateCode(String player) { String code = RandomStringUtils.generateHex(recoveryCodeLength); - recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis)); + recoveryCodes.put(player, code); return code; } @@ -55,11 +55,8 @@ public class RecoveryCodeService implements SettingsDependent { * @return true if the code matches and has not expired, false otherwise */ public boolean isCodeValid(String player, String code) { - ExpiringEntry entry = recoveryCodes.get(player); - if (entry != null) { - return code != null && code.equals(entry.getCode()); - } - return false; + String storedCode = recoveryCodes.get(player); + return storedCode != null && storedCode.equals(code); } /** @@ -74,26 +71,12 @@ public class RecoveryCodeService implements SettingsDependent { @Override public void reload(Settings settings) { recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH); - recoveryCodeExpirationMillis = settings.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR; + recoveryCodeExpiration = settings.getProperty(RECOVERY_CODE_HOURS_VALID); + recoveryCodes.setExpiration(recoveryCodeExpiration, TimeUnit.HOURS); } - /** - * Entry with an expiration. - */ - @VisibleForTesting - static final class ExpiringEntry { - - private final String code; - private final long expiration; - - ExpiringEntry(String code, long expiration) { - this.code = code; - this.expiration = expiration; - } - - String getCode() { - return System.currentTimeMillis() < expiration ? code : null; - } + @Override + public void performCleanup() { + recoveryCodes.removeExpiredEntries(); } - } diff --git a/src/main/java/fr/xephi/authme/util/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/ExpiringMap.java new file mode 100644 index 00000000..83beb37d --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/ExpiringMap.java @@ -0,0 +1,114 @@ +package fr.xephi.authme.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * Map with expiring entries. Following a configured amount of time after + * an entry has been inserted, the map will act as if the entry does not + * exist. + *

+ * Time starts counting directly after insertion. Inserting a new entry with + * a key that already has a value will "reset" the expiration. Although the + * expiration can be redefined later on, only entries which are inserted + * afterwards will use the new expiration. + *

+ * An expiration of {@code <= 0} will make the map expire all entries + * immediately after insertion. Note that the map does not remove expired + * entries automatically; this is only done when calling + * {@link #removeExpiredEntries()}. + * + * @param the key type + * @param the value type + */ +public class ExpiringMap { + + private final Map> entries = new ConcurrentHashMap<>(); + private long expirationMillis; + + /** + * Constructor. + * + * @param duration the duration of time after which entries expire + * @param unit the time unit in which {@code duration} is expressed + */ + public ExpiringMap(long duration, TimeUnit unit) { + setExpiration(duration, unit); + } + + /** + * Returns the value associated with the given key, + * if available and not expired. + * + * @param key the key to look up + * @return the associated value, or {@code null} if not available + */ + public V get(K key) { + ExpiringEntry value = entries.get(key); + return value == null ? null : value.getValue(); + } + + /** + * Inserts a value for the given key. Overwrites a previous value + * for the key if it exists. + * + * @param key the key to insert a value for + * @param value the value to insert + */ + public void put(K key, V value) { + long expiration = System.currentTimeMillis() + expirationMillis; + entries.put(key, new ExpiringEntry<>(value, expiration)); + } + + /** + * Removes the value for the given key, if available. + * + * @param key the key to remove the value for + */ + public void remove(K key) { + entries.remove(key); + } + + /** + * Removes all entries which have expired from the internal structure. + */ + public void removeExpiredEntries() { + entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue().getExpiration()); + } + + /** + * Sets a new expiration duration. Note that already present entries + * will still make use of the old expiration. + * + * @param duration the duration of time after which entries expire + * @param unit the time unit in which {@code duration} is expressed + */ + public void setExpiration(long duration, TimeUnit unit) { + this.expirationMillis = unit.toMillis(duration); + } + + /** + * Class holding a value paired with an expiration timestamp. + * + * @param the value type + */ + private static final class ExpiringEntry { + + private final V value; + private final long expiration; + + ExpiringEntry(V value, long expiration) { + this.value = value; + this.expiration = expiration; + } + + V getValue() { + return System.currentTimeMillis() > expiration ? null : value; + } + + long getExpiration() { + return expiration; + } + } +} diff --git a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java index b06a01f8..576c5da3 100644 --- a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java @@ -4,15 +4,13 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.service.RecoveryCodeService.ExpiringEntry; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.ExpiringMap; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import java.util.Map; - import static fr.xephi.authme.AuthMeMatchers.stringWithLength; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; @@ -60,22 +58,8 @@ public class RecoveryCodeServiceTest { recoveryCodeService.generateCode(name); // then - ExpiringEntry entry = getCodeMap().get(name); - assertThat(entry.getCode(), stringWithLength(5)); - } - - @Test - public void shouldNotConsiderExpiredCode() { - // given - String player = "Cat"; - String code = "11F235"; - setCodeInMap(player, code, System.currentTimeMillis() - 500); - - // when - boolean result = recoveryCodeService.isCodeValid(player, code); - - // then - assertThat(result, equalTo(false)); + String code = getCodeMap().get(name); + assertThat(code, stringWithLength(5)); } @Test @@ -106,12 +90,7 @@ public class RecoveryCodeServiceTest { } - private Map getCodeMap() { + private ExpiringMap getCodeMap() { return ReflectionTestUtils.getFieldValue(RecoveryCodeService.class, recoveryCodeService, "recoveryCodes"); } - - private void setCodeInMap(String player, String code, long expiration) { - Map map = getCodeMap(); - map.put(player, new ExpiringEntry(code, expiration)); - } } diff --git a/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java b/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java new file mode 100644 index 00000000..d4a3f868 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java @@ -0,0 +1,94 @@ +package fr.xephi.authme.util; + +import fr.xephi.authme.ReflectionTestUtils; +import org.junit.Test; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link ExpiringMap}. + */ +public class ExpiringMapTest { + + @Test + public void shouldAddAndRetrieveEntries() { + // given + ExpiringMap map = new ExpiringMap<>(3, TimeUnit.MINUTES); + + // when / then + map.put("three", 3.0); + map.put("treefiddy", 3.50); + + assertThat(map.get("three"), equalTo(3.0)); + assertThat(map.get("treefiddy"), equalTo(3.50)); + } + + @Test + public void shouldRemoveEntry() { + // given + ExpiringMap map = new ExpiringMap<>(1, TimeUnit.HOURS); + map.put("hi", true); + map.put("ha", false); + + // when + map.remove("ha"); + + // then + assertThat(map.get("ha"), nullValue()); + assertThat(map.get("hi"), equalTo(true)); + } + + @Test + public void shouldUpdateExpiration() { + // given + ExpiringMap map = new ExpiringMap<>(2, TimeUnit.DAYS); + map.put(2, 4); + map.put(3, 9); + + // when + map.setExpiration(0, TimeUnit.SECONDS); + + // then + map.put(5, 25); + assertThat(map.get(2), equalTo(4)); + assertThat(map.get(3), equalTo(9)); + assertThat(map.get(5), nullValue()); + } + + @Test + public void shouldAcceptNegativeExpiration() { + // given / when + ExpiringMap map = new ExpiringMap<>(-3, TimeUnit.MINUTES); + map.put(3, "trois"); + + // then + assertThat(map.get(3), nullValue()); + } + + @Test + public void shouldCleanUpExpiredEntries() throws InterruptedException { + // given + ExpiringMap map = new ExpiringMap<>(400, TimeUnit.MILLISECONDS); + map.put(144, 12); + map.put(121, 11); + map.put(81, 9); + map.setExpiration(900, TimeUnit.MILLISECONDS); + map.put(64, 8); + map.put(25, 5); + + // when + Thread.sleep(400); + map.removeExpiredEntries(); + + // then + Map internalMap = ReflectionTestUtils.getFieldValue(ExpiringMap.class, map, "entries"); + assertThat(internalMap.keySet(), containsInAnyOrder(64, 25)); + } + +} From 152d1dc2167089fb33da951e2e2e780b85e9e565 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 22:50:30 +0100 Subject: [PATCH 30/79] #949 Created TimedCounter + implement it in TempbanManager --- .../fr/xephi/authme/data/TempbanManager.java | 90 ++++--------------- .../fr/xephi/authme/util/ExpiringMap.java | 15 +++- .../fr/xephi/authme/util/TimedCounter.java | 50 +++++++++++ .../xephi/authme/data/TempbanManagerTest.java | 64 +++++-------- .../fr/xephi/authme/util/ExpiringMapTest.java | 33 +++---- .../xephi/authme/util/TimedCounterTest.java | 55 ++++++++++++ 6 files changed, 172 insertions(+), 135 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/util/TimedCounter.java create mode 100644 src/test/java/fr/xephi/authme/util/TimedCounterTest.java diff --git a/src/main/java/fr/xephi/authme/data/TempbanManager.java b/src/main/java/fr/xephi/authme/data/TempbanManager.java index e5d31ed1..07019a72 100644 --- a/src/main/java/fr/xephi/authme/data/TempbanManager.java +++ b/src/main/java/fr/xephi/authme/data/TempbanManager.java @@ -1,21 +1,21 @@ package fr.xephi.authme.data; -import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.util.TimedCounter; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; import javax.inject.Inject; import java.util.Date; -import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import static fr.xephi.authme.settings.properties.SecuritySettings.TEMPBAN_MINUTES_BEFORE_RESET; import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE; @@ -25,7 +25,7 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE; */ public class TempbanManager implements SettingsDependent, HasCleanup { - private final Map> ipLoginFailureCounts; + private final Map> ipLoginFailureCounts; private final BukkitService bukkitService; private final Messages messages; @@ -50,18 +50,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup { */ public void increaseCount(String address, String name) { if (isEnabled) { - Map countsByName = ipLoginFailureCounts.get(address); - if (countsByName == null) { - countsByName = new ConcurrentHashMap<>(); - ipLoginFailureCounts.put(address, countsByName); - } - - TimedCounter counter = countsByName.get(name); - if (counter == null) { - countsByName.put(name, new TimedCounter(1)); - } else { - counter.increment(resetThreshold); - } + TimedCounter countsByName = ipLoginFailureCounts.computeIfAbsent( + address, k -> new TimedCounter<>(resetThreshold, TimeUnit.MINUTES)); + countsByName.increment(name); } } @@ -73,9 +64,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup { */ public void resetCount(String address, String name) { if (isEnabled) { - Map map = ipLoginFailureCounts.get(address); - if (map != null) { - map.remove(name); + TimedCounter counter = ipLoginFailureCounts.get(address); + if (counter != null) { + counter.remove(name); } } } @@ -88,13 +79,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup { */ public boolean shouldTempban(String address) { if (isEnabled) { - Map countsByName = ipLoginFailureCounts.get(address); + TimedCounter countsByName = ipLoginFailureCounts.get(address); if (countsByName != null) { - int total = 0; - for (TimedCounter counter : countsByName.values()) { - total += counter.getCount(resetThreshold); - } - return total >= threshold; + return countsByName.total() >= threshold; } } return false; @@ -137,56 +124,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup { @Override public void performCleanup() { - for (Map countsByIp : ipLoginFailureCounts.values()) { - Iterator it = countsByIp.values().iterator(); - while (it.hasNext()) { - TimedCounter counter = it.next(); - if (counter.getCount(resetThreshold) == 0) { - it.remove(); - } - } - } - } - - /** - * Counter with an associated timestamp, keeping track of when the last entry has been added. - */ - @VisibleForTesting - static final class TimedCounter { - - private int counter; - private long lastIncrementTimestamp = System.currentTimeMillis(); - - /** - * Constructor. - * - * @param start the initial value to set the counter to - */ - TimedCounter(int start) { - this.counter = start; - } - - /** - * Returns the count, taking into account the last entry timestamp. - * - * @param threshold the threshold in milliseconds until when to consider a counter - * @return the counter's value, or {@code 0} if it was last incremented longer ago than the threshold - */ - int getCount(long threshold) { - if (System.currentTimeMillis() - lastIncrementTimestamp > threshold) { - return 0; - } - return counter; - } - - /** - * Increments the counter, taking into account the last entry timestamp. - * - * @param threshold in milliseconds, the time span until which to consider the existing number - */ - void increment(long threshold) { - counter = getCount(threshold) + 1; - lastIncrementTimestamp = System.currentTimeMillis(); + for (TimedCounter countsByIp : ipLoginFailureCounts.values()) { + countsByIp.removeExpiredEntries(); } + ipLoginFailureCounts.entrySet().removeIf(e -> e.getValue().isEmpty()); } } diff --git a/src/main/java/fr/xephi/authme/util/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/ExpiringMap.java index 83beb37d..519946f8 100644 --- a/src/main/java/fr/xephi/authme/util/ExpiringMap.java +++ b/src/main/java/fr/xephi/authme/util/ExpiringMap.java @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit; */ public class ExpiringMap { - private final Map> entries = new ConcurrentHashMap<>(); + protected final Map> entries = new ConcurrentHashMap<>(); private long expirationMillis; /** @@ -88,12 +88,23 @@ public class ExpiringMap { this.expirationMillis = unit.toMillis(duration); } + /** + * Returns whether this map is empty. This reflects the state of the + * internal map, which may contain expired entries only. The result + * may change after running {@link #removeExpiredEntries()}. + * + * @return true if map is really empty, false otherwise + */ + public boolean isEmpty() { + return entries.isEmpty(); + } + /** * Class holding a value paired with an expiration timestamp. * * @param the value type */ - private static final class ExpiringEntry { + protected static final class ExpiringEntry { private final V value; private final long expiration; diff --git a/src/main/java/fr/xephi/authme/util/TimedCounter.java b/src/main/java/fr/xephi/authme/util/TimedCounter.java new file mode 100644 index 00000000..59c54d4d --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/TimedCounter.java @@ -0,0 +1,50 @@ +package fr.xephi.authme.util; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * Keeps a count per key which expires after a configurable amount of time. + *

+ * Once the expiration of an entry has been reached, the counter resets + * to 0. The counter returns 0 rather than {@code null} for any given key. + */ +public class TimedCounter extends ExpiringMap { + + /** + * Constructor. + * + * @param duration the duration of time after which entries expire + * @param unit the time unit in which {@code duration} is expressed + */ + public TimedCounter(long duration, TimeUnit unit) { + super(duration, unit); + } + + @Override + public Integer get(K key) { + Integer value = super.get(key); + return value == null ? 0 : value; + } + + /** + * Increments the value stored for the provided key. + * + * @param key the key to increment the counter for + */ + public void increment(K key) { + put(key, get(key) + 1); + } + + /** + * Calculates the total of all non-expired entries in this counter. + * + * @return the total of all valid entries + */ + public int total() { + return entries.values().stream() + .map(ExpiringEntry::getValue) + .filter(Objects::nonNull) + .reduce(0, Integer::sum); + } +} diff --git a/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java b/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java index 28c2ab9e..4fd8ad11 100644 --- a/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java @@ -2,12 +2,12 @@ package fr.xephi.authme.data; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.data.TempbanManager.TimedCounter; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.TimedCounter; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -20,8 +20,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertThat; @@ -195,42 +194,24 @@ public class TempbanManagerTest { @Test public void shouldPerformCleanup() { // given - // `expirationPoint` is the approximate timestamp until which entries should be considered, so subtracting - // from it will create expired entries, and adding a reasonably large number makes it still valid - final long expirationPoint = System.currentTimeMillis() - TEST_EXPIRATION_THRESHOLD; - // 2 current entries with total 6 failed tries - Map map1 = new HashMap<>(); - map1.put("name", newTimedCounter(4, expirationPoint + 20_000)); - map1.put("other", newTimedCounter(2, expirationPoint + 40_000)); - // 0 current entries - Map map2 = new HashMap<>(); - map2.put("someone", newTimedCounter(10, expirationPoint - 5_000)); - map2.put("somebody", newTimedCounter(10, expirationPoint - 8_000)); - // 1 current entry with total 4 failed tries - Map map3 = new HashMap<>(); - map3.put("some", newTimedCounter(5, expirationPoint - 12_000)); - map3.put("test", newTimedCounter(4, expirationPoint + 8_000)); - map3.put("values", newTimedCounter(2, expirationPoint - 80_000)); + Map> counts = new HashMap<>(); + TimedCounter counter1 = mockCounter(); + given(counter1.isEmpty()).willReturn(true); + counts.put("11.11.11.11", counter1); + TimedCounter counter2 = mockCounter(); + given(counter2.isEmpty()).willReturn(false); + counts.put("33.33.33.33", counter2); - String[] addresses = {"123.45.67.89", "127.0.0.1", "192.168.0.1"}; - Map> counterMap = new HashMap<>(); - counterMap.put(addresses[0], map1); - counterMap.put(addresses[1], map2); - counterMap.put(addresses[2], map3); - - TempbanManager manager = new TempbanManager(bukkitService, messages, mockSettings(5, 250)); - ReflectionTestUtils.setField(TempbanManager.class, manager, "ipLoginFailureCounts", counterMap); + TempbanManager manager = new TempbanManager(bukkitService, messages, mockSettings(3, 10)); + ReflectionTestUtils.setField(TempbanManager.class, manager, "ipLoginFailureCounts", counts); // when manager.performCleanup(); // then - assertThat(counterMap.get(addresses[0]), aMapWithSize(2)); - assertHasCount(manager, addresses[0], "name", 4); - assertHasCount(manager, addresses[0], "other", 2); - assertThat(counterMap.get(addresses[1]), anEmptyMap()); - assertThat(counterMap.get(addresses[2]), aMapWithSize(1)); - assertHasCount(manager, addresses[2], "test", 4); + verify(counter1).removeExpiredEntries(); + verify(counter2).removeExpiredEntries(); + assertThat(counts.keySet(), contains("33.33.33.33")); } private static Settings mockSettings(int maxTries, int tempbanLength) { @@ -244,21 +225,20 @@ public class TempbanManagerTest { } private static void assertHasNoEntries(TempbanManager manager, String address) { - Map> playerCounts = ReflectionTestUtils + Map> playerCounts = ReflectionTestUtils .getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts"); - Map map = playerCounts.get(address); - assertThat(map == null || map.isEmpty(), equalTo(true)); + TimedCounter counter = playerCounts.get(address); + assertThat(counter == null || counter.isEmpty(), equalTo(true)); } private static void assertHasCount(TempbanManager manager, String address, String name, int count) { - Map> playerCounts = ReflectionTestUtils + Map> playerCounts = ReflectionTestUtils .getFieldValue(TempbanManager.class, manager, "ipLoginFailureCounts"); - assertThat(playerCounts.get(address).get(name).getCount(TEST_EXPIRATION_THRESHOLD), equalTo(count)); + assertThat(playerCounts.get(address).get(name), equalTo(count)); } - private static TimedCounter newTimedCounter(int count, long timestamp) { - TimedCounter counter = new TimedCounter(count); - ReflectionTestUtils.setField(TimedCounter.class, counter, "lastIncrementTimestamp", timestamp); - return counter; + @SuppressWarnings("unchecked") + private static TimedCounter mockCounter() { + return mock(TimedCounter.class); } } diff --git a/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java b/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java index d4a3f868..cdc9c36a 100644 --- a/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java +++ b/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.util; -import fr.xephi.authme.ReflectionTestUtils; import org.junit.Test; import java.util.Map; @@ -45,14 +44,14 @@ public class ExpiringMapTest { } @Test - public void shouldUpdateExpiration() { + public void shouldUpdateExpirationAndSupportNegativeValues() { // given ExpiringMap map = new ExpiringMap<>(2, TimeUnit.DAYS); map.put(2, 4); map.put(3, 9); // when - map.setExpiration(0, TimeUnit.SECONDS); + map.setExpiration(-100, TimeUnit.MILLISECONDS); // then map.put(5, 25); @@ -61,20 +60,10 @@ public class ExpiringMapTest { assertThat(map.get(5), nullValue()); } - @Test - public void shouldAcceptNegativeExpiration() { - // given / when - ExpiringMap map = new ExpiringMap<>(-3, TimeUnit.MINUTES); - map.put(3, "trois"); - - // then - assertThat(map.get(3), nullValue()); - } - @Test public void shouldCleanUpExpiredEntries() throws InterruptedException { // given - ExpiringMap map = new ExpiringMap<>(400, TimeUnit.MILLISECONDS); + ExpiringMap map = new ExpiringMap<>(200, TimeUnit.MILLISECONDS); map.put(144, 12); map.put(121, 11); map.put(81, 9); @@ -83,12 +72,24 @@ public class ExpiringMapTest { map.put(25, 5); // when - Thread.sleep(400); + Thread.sleep(300); map.removeExpiredEntries(); // then - Map internalMap = ReflectionTestUtils.getFieldValue(ExpiringMap.class, map, "entries"); + Map internalMap = map.entries; assertThat(internalMap.keySet(), containsInAnyOrder(64, 25)); } + @Test + public void shouldReturnIfIsEmpty() { + // given + ExpiringMap map = new ExpiringMap<>(-8, TimeUnit.SECONDS); + + // when / then + assertThat(map.isEmpty(), equalTo(true)); + map.put("hoi", "Welt"); + assertThat(map.isEmpty(), equalTo(false)); + map.removeExpiredEntries(); + assertThat(map.isEmpty(), equalTo(true)); + } } diff --git a/src/test/java/fr/xephi/authme/util/TimedCounterTest.java b/src/test/java/fr/xephi/authme/util/TimedCounterTest.java new file mode 100644 index 00000000..9903842d --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/TimedCounterTest.java @@ -0,0 +1,55 @@ +package fr.xephi.authme.util; + +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link TimedCounter}. + */ +public class TimedCounterTest { + + @Test + public void shouldReturnZeroForAnyKey() { + // given + TimedCounter counter = new TimedCounter<>(1, TimeUnit.DAYS); + + // when / then + assertThat(counter.get(2.0), equalTo(0)); + assertThat(counter.get(-3.14159), equalTo(0)); + } + + @Test + public void shouldIncrementCount() { + // given + TimedCounter counter = new TimedCounter<>(10, TimeUnit.MINUTES); + counter.put("moto", 12); + + // when + counter.increment("hello"); + counter.increment("moto"); + + // then + assertThat(counter.get("hello"), equalTo(1)); + assertThat(counter.get("moto"), equalTo(13)); + } + + @Test + public void shouldSumUpEntries() { + // given + TimedCounter counter = new TimedCounter<>(90, TimeUnit.SECONDS); + counter.entries.put("expired", new ExpiringMap.ExpiringEntry<>(800, 0)); + counter.entries.put("expired2", new ExpiringMap.ExpiringEntry<>(24, System.currentTimeMillis() - 100)); + counter.put("other", 10); + counter.put("Another", 4); + + // when + int totals = counter.total(); + + // then + assertThat(totals, equalTo(14)); + } +} From 7b3bd3f4ea2534bbabe4304b80d7139b942902f7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Feb 2017 23:00:19 +0100 Subject: [PATCH 31/79] Make the Travis build great again Curious that only TravisCI has issues with some lambda code creating a map. Both CircleCI and our project Jenkins are happy with it. The same JDK is configured for TravisCI and CircleCI, too... --- .../executable/authme/debug/DebugCommand.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index 8e7168f2..9d67da39 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -6,11 +6,10 @@ import fr.xephi.authme.initialization.factory.Factory; import org.bukkit.command.CommandSender; import javax.inject.Inject; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; /** * Debug command main. @@ -44,9 +43,12 @@ public class DebugCommand implements ExecutableCommand { // Lazy getter private Map getSections() { if (sections == null) { - sections = sectionClasses.stream() - .map(debugSectionFactory::newInstance) - .collect(Collectors.toMap(DebugSection::getName, Function.identity())); + Map sections = new HashMap<>(); + for (Class sectionClass : sectionClasses) { + DebugSection section = debugSectionFactory.newInstance(sectionClass); + sections.put(section.getName(), section); + } + this.sections = sections; } return sections; } From ca708e23cd197e69e6970f34fb884acf0e9d8bc0 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 19 Feb 2017 09:06:15 +0100 Subject: [PATCH 32/79] #949 Create ExpiringSet, integrate into SessionManager --- .../fr/xephi/authme/data/SessionManager.java | 44 +++------ .../fr/xephi/authme/util/ExpiringSet.java | 97 +++++++++++++++++++ .../xephi/authme/data/SessionManagerTest.java | 57 ++++------- .../fr/xephi/authme/util/ExpiringSetTest.java | 91 +++++++++++++++++ 4 files changed, 220 insertions(+), 69 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/util/ExpiringSet.java create mode 100644 src/test/java/fr/xephi/authme/util/ExpiringSetTest.java diff --git a/src/main/java/fr/xephi/authme/data/SessionManager.java b/src/main/java/fr/xephi/authme/data/SessionManager.java index f86f5421..23da3aff 100644 --- a/src/main/java/fr/xephi/authme/data/SessionManager.java +++ b/src/main/java/fr/xephi/authme/data/SessionManager.java @@ -5,13 +5,10 @@ import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.util.ExpiringSet; import javax.inject.Inject; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE; +import java.util.concurrent.TimeUnit; /** * Manages sessions, allowing players to be automatically logged in if they join again @@ -19,15 +16,14 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE; */ public class SessionManager implements SettingsDependent, HasCleanup { - // Player -> expiration of session in milliseconds - private final Map sessions = new ConcurrentHashMap<>(); - + private final ExpiringSet sessions; private boolean enabled; - private int timeoutInMinutes; @Inject SessionManager(Settings settings) { - reload(settings); + long timeout = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT); + sessions = new ExpiringSet<>(timeout, TimeUnit.MINUTES); + enabled = timeout > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED); } /** @@ -37,13 +33,7 @@ public class SessionManager implements SettingsDependent, HasCleanup { * @return True if a session is found. */ public boolean hasSession(String name) { - if (enabled) { - Long timeout = sessions.get(name.toLowerCase()); - if (timeout != null) { - return System.currentTimeMillis() <= timeout; - } - } - return false; + return enabled && sessions.contains(name.toLowerCase()); } /** @@ -53,8 +43,7 @@ public class SessionManager implements SettingsDependent, HasCleanup { */ public void addSession(String name) { if (enabled) { - long timeout = System.currentTimeMillis() + timeoutInMinutes * MILLIS_PER_MINUTE; - sessions.put(name.toLowerCase(), timeout); + sessions.add(name.toLowerCase()); } } @@ -64,12 +53,13 @@ public class SessionManager implements SettingsDependent, HasCleanup { * @param name The name of the player. */ public void removeSession(String name) { - this.sessions.remove(name.toLowerCase()); + sessions.remove(name.toLowerCase()); } @Override public void reload(Settings settings) { - timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT); + long timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT); + sessions.setExpiration(timeoutInMinutes, TimeUnit.MINUTES); boolean oldEnabled = enabled; enabled = timeoutInMinutes > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED); @@ -82,16 +72,8 @@ public class SessionManager implements SettingsDependent, HasCleanup { @Override public void performCleanup() { - if (!enabled) { - return; - } - final long currentTime = System.currentTimeMillis(); - Iterator> iterator = sessions.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (entry.getValue() < currentTime) { - iterator.remove(); - } + if (enabled) { + sessions.removeExpiredEntries(); } } } diff --git a/src/main/java/fr/xephi/authme/util/ExpiringSet.java b/src/main/java/fr/xephi/authme/util/ExpiringSet.java new file mode 100644 index 00000000..840d5911 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/ExpiringSet.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * Set whose entries expire after a configurable amount of time. Once an entry + * has expired, the set will act as if the entry no longer exists. Time starts + * counting after the entry has been inserted. + *

+ * Internally, expired entries are not cleared automatically. A cleanup can be + * triggered with {@link #removeExpiredEntries()}. Adding an entry that is + * already present effectively resets its expiration. + * + * @param the type of the entries + */ +public class ExpiringSet { + + private Map entries = new ConcurrentHashMap<>(); + private long expirationMillis; + + /** + * Constructor. + * + * @param duration the duration of time after which entries expire + * @param unit the time unit in which {@code duration} is expressed + */ + public ExpiringSet(long duration, TimeUnit unit) { + setExpiration(duration, unit); + } + + /** + * Adds an entry to the set. + * + * @param entry the entry to add + */ + public void add(E entry) { + entries.put(entry, System.currentTimeMillis() + expirationMillis); + } + + /** + * Returns whether this set contains the given entry, if it hasn't expired. + * + * @param entry the entry to check + * @return true if the entry is present and not expired, false otherwise + */ + public boolean contains(E entry) { + Long expiration = entries.get(entry); + return expiration != null && expiration > System.currentTimeMillis(); + } + + /** + * Removes the given entry from the set (if present). + * + * @param entry the entry to remove + */ + public void remove(E entry) { + entries.remove(entry); + } + + /** + * Removes all entries from the set. + */ + public void clear() { + entries.clear(); + } + + /** + * Removes all entries which have expired from the internal structure. + */ + public void removeExpiredEntries() { + entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue()); + } + + /** + * Sets a new expiration duration. Note that already present entries + * will still make use of the old expiration. + * + * @param duration the duration of time after which entries expire + * @param unit the time unit in which {@code duration} is expressed + */ + public void setExpiration(long duration, TimeUnit unit) { + this.expirationMillis = unit.toMillis(duration); + } + + /** + * Returns whether this map is empty. This reflects the state of the + * internal map, which may contain expired entries only. The result + * may change after running {@link #removeExpiredEntries()}. + * + * @return true if map is really empty, false otherwise + */ + public boolean isEmpty() { + return entries.isEmpty(); + } +} diff --git a/src/test/java/fr/xephi/authme/data/SessionManagerTest.java b/src/test/java/fr/xephi/authme/data/SessionManagerTest.java index f01311af..50b18178 100644 --- a/src/test/java/fr/xephi/authme/data/SessionManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/SessionManagerTest.java @@ -3,19 +3,17 @@ package fr.xephi.authme.data; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.util.ExpiringSet; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; -import java.util.Map; - -import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * Test for {@link SessionManager}. @@ -91,24 +89,6 @@ public class SessionManagerTest { assertThat(manager.hasSession(player), equalTo(false)); } - @Test - public void shouldDenySessionIfTimeoutHasExpired() { - // given - int timeout = 20; - Settings settings = mockSettings(true, timeout); - String player = "patrick"; - SessionManager manager = new SessionManager(settings); - Map sessions = getSessionsMap(manager); - // Add session entry for player that just has expired - sessions.put(player, System.currentTimeMillis() - 1000); - - // when - boolean result = manager.hasSession(player); - - // then - assertThat(result, equalTo(false)); - } - @Test public void shouldClearAllSessionsAfterDisable() { // given @@ -121,7 +101,7 @@ public class SessionManagerTest { manager.reload(mockSettings(false, 20)); // then - assertThat(getSessionsMap(manager), anEmptyMap()); + assertThat(getSessionsMap(manager).isEmpty(), equalTo(true)); } @Test @@ -129,18 +109,14 @@ public class SessionManagerTest { // given Settings settings = mockSettings(true, 1); SessionManager manager = new SessionManager(settings); - Map sessions = getSessionsMap(manager); - sessions.put("somebody", System.currentTimeMillis() - 123L); - sessions.put("someone", System.currentTimeMillis() + 4040L); - sessions.put("anyone", System.currentTimeMillis() - 1000L); - sessions.put("everyone", System.currentTimeMillis() + 60000L); + ExpiringSet expiringSet = mockExpiringSet(); + setSessionsMap(manager, expiringSet); // when manager.performCleanup(); // then - assertThat(sessions, aMapWithSize(2)); - assertThat(sessions.keySet(), containsInAnyOrder("someone", "everyone")); + verify(expiringSet).removeExpiredEntries(); } @Test @@ -148,23 +124,28 @@ public class SessionManagerTest { // given Settings settings = mockSettings(false, 1); SessionManager manager = new SessionManager(settings); - Map sessions = getSessionsMap(manager); - sessions.put("somebody", System.currentTimeMillis() - 123L); - sessions.put("someone", System.currentTimeMillis() + 4040L); - sessions.put("anyone", System.currentTimeMillis() - 1000L); - sessions.put("everyone", System.currentTimeMillis() + 60000L); + ExpiringSet expiringSet = mockExpiringSet(); + setSessionsMap(manager, expiringSet); // when manager.performCleanup(); // then - assertThat(sessions, aMapWithSize(4)); // map not changed -> no cleanup performed + verify(expiringSet, never()).removeExpiredEntries(); } - private static Map getSessionsMap(SessionManager manager) { + private static ExpiringSet getSessionsMap(SessionManager manager) { return ReflectionTestUtils.getFieldValue(SessionManager.class, manager, "sessions"); } + private static void setSessionsMap(SessionManager manager, ExpiringSet sessionsMap) { + ReflectionTestUtils.setField(SessionManager.class, manager, "sessions", sessionsMap); + } + + @SuppressWarnings("unchecked") + private static ExpiringSet mockExpiringSet() { + return mock(ExpiringSet.class); + } private static Settings mockSettings(boolean isEnabled, int sessionTimeout) { Settings settings = mock(Settings.class); diff --git a/src/test/java/fr/xephi/authme/util/ExpiringSetTest.java b/src/test/java/fr/xephi/authme/util/ExpiringSetTest.java new file mode 100644 index 00000000..f58e0312 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/ExpiringSetTest.java @@ -0,0 +1,91 @@ +package fr.xephi.authme.util; + +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link ExpiringSet}. + */ +public class ExpiringSetTest { + + @Test + public void shouldAddEntry() { + // given + ExpiringSet set = new ExpiringSet<>(10, TimeUnit.MINUTES); + + // when + set.add("authme"); + + // then + assertThat(set.contains("authme"), equalTo(true)); + assertThat(set.contains("other"), equalTo(false)); + } + + @Test + public void shouldRemoveEntries() { + // given + ExpiringSet set = new ExpiringSet<>(20, TimeUnit.SECONDS); + set.add(20); + set.add(40); + + // when + set.remove(40); + set.remove(60); + + // then + assertThat(set.contains(20), equalTo(true)); + assertThat(set.contains(40), equalTo(false)); + assertThat(set.contains(60), equalTo(false)); + } + + @Test + public void shouldHandleNewExpirationAndSupportNegativeValues() { + // given + ExpiringSet set = new ExpiringSet<>(800, TimeUnit.MILLISECONDS); + set.add('A'); + + // when + set.setExpiration(-10, TimeUnit.SECONDS); + set.add('Y'); + + // then + assertThat(set.contains('A'), equalTo(true)); + assertThat(set.contains('Y'), equalTo(false)); + } + + @Test + public void shouldClearAllValues() { + // given + ExpiringSet set = new ExpiringSet<>(1, TimeUnit.MINUTES); + set.add("test"); + + // when / then + assertThat(set.isEmpty(), equalTo(false)); + set.clear(); + assertThat(set.isEmpty(), equalTo(true)); + assertThat(set.contains("test"), equalTo(false)); + } + + @Test + public void shouldClearExpiredValues() { + // given + ExpiringSet set = new ExpiringSet<>(2, TimeUnit.HOURS); + set.add(2); + set.setExpiration(-100, TimeUnit.SECONDS); + set.add(3); + set.setExpiration(20, TimeUnit.MINUTES); + set.add(6); + + // when + set.removeExpiredEntries(); + + // then + assertThat(set.contains(2), equalTo(true)); + assertThat(set.contains(3), equalTo(false)); + assertThat(set.contains(6), equalTo(true)); + } +} From 510826d2684b7eb1dacd5ea892f31b1650a4b47e Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 19 Feb 2017 11:34:56 +0100 Subject: [PATCH 33/79] Add manifest file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 573eb061..82df141d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ ### Java files ### *.class +MANIFEST.MF # Package Files #*.jar From 39395836b44730257b6b8288108ad9c76e418277 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 19 Feb 2017 11:50:06 +0100 Subject: [PATCH 34/79] #949 Add configurable timeout for captcha count --- .../fr/xephi/authme/data/CaptchaManager.java | 44 ++++++++----------- .../settings/properties/SecuritySettings.java | 4 ++ .../xephi/authme/data/CaptchaManagerTest.java | 30 +++++-------- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/main/java/fr/xephi/authme/data/CaptchaManager.java b/src/main/java/fr/xephi/authme/data/CaptchaManager.java index 36f33a3c..7e83ec23 100644 --- a/src/main/java/fr/xephi/authme/data/CaptchaManager.java +++ b/src/main/java/fr/xephi/authme/data/CaptchaManager.java @@ -1,19 +1,22 @@ package fr.xephi.authme.data; +import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; -import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.RandomStringUtils; +import fr.xephi.authme.util.TimedCounter; import javax.inject.Inject; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * Manager for the handling of captchas. */ -public class CaptchaManager implements SettingsDependent { +public class CaptchaManager implements SettingsDependent, HasCleanup { - private final ConcurrentHashMap playerCounts; + private final TimedCounter playerCounts; private final ConcurrentHashMap captchaCodes; private boolean isEnabled; @@ -22,8 +25,9 @@ public class CaptchaManager implements SettingsDependent { @Inject CaptchaManager(Settings settings) { - this.playerCounts = new ConcurrentHashMap<>(); this.captchaCodes = new ConcurrentHashMap<>(); + long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET); + this.playerCounts = new TimedCounter<>(countTimeout, TimeUnit.MINUTES); reload(settings); } @@ -35,12 +39,7 @@ public class CaptchaManager implements SettingsDependent { public void increaseCount(String name) { if (isEnabled) { String playerLower = name.toLowerCase(); - Integer currentCount = playerCounts.get(playerLower); - if (currentCount == null) { - playerCounts.put(playerLower, 1); - } else { - playerCounts.put(playerLower, currentCount + 1); - } + playerCounts.increment(playerLower); } } @@ -51,21 +50,7 @@ public class CaptchaManager implements SettingsDependent { * @return true if the player has to solve a captcha, false otherwise */ public boolean isCaptchaRequired(String name) { - if (isEnabled) { - Integer count = playerCounts.get(name.toLowerCase()); - return count != null && count >= threshold; - } - return false; - } - - /** - * Returns the stored captcha code for the player. - * - * @param name the player's name - * @return the code the player is required to enter, or null if none registered - */ - public String getCaptchaCode(String name) { - return captchaCodes.get(name.toLowerCase()); + return isEnabled && playerCounts.get(name.toLowerCase()) >= threshold; } /** @@ -75,7 +60,7 @@ public class CaptchaManager implements SettingsDependent { * @return the code the player is required to enter */ public String getCaptchaCodeOrGenerateNew(String name) { - String code = getCaptchaCode(name); + String code = captchaCodes.get(name.toLowerCase()); return code == null ? generateCode(name) : code; } @@ -127,6 +112,13 @@ public class CaptchaManager implements SettingsDependent { this.isEnabled = settings.getProperty(SecuritySettings.USE_CAPTCHA); this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA); this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH); + long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET); + playerCounts.setExpiration(countTimeout, TimeUnit.MINUTES); + } + + @Override + public void performCleanup() { + playerCounts.removeExpiredEntries(); } } diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 054651ad..daed1a8c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -40,6 +40,10 @@ public class SecuritySettings implements SettingsHolder { public static final Property CAPTCHA_LENGTH = newProperty("Security.captcha.captchaLength", 5); + @Comment("Minutes after which login attempts count is reset for a player") + public static final Property CAPTCHA_COUNT_MINUTES_BEFORE_RESET = + newProperty("Security.captcha.captchaCountReset", 60); + @Comment("Minimum length of password") public static final Property MIN_PASSWORD_LENGTH = newProperty("settings.security.minPasswordLength", 5); diff --git a/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java b/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java index 92be450d..d53091db 100644 --- a/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java @@ -3,12 +3,10 @@ package fr.xephi.authme.data; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.TimedCounter; import org.junit.Test; -import java.util.Map; - import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -57,11 +55,6 @@ public class CaptchaManagerTest { assertThat(manager.checkCode(player, "bogus"), equalTo(true)); } - /** - * Tests {@link CaptchaManager#getCaptchaCode} and {@link CaptchaManager#getCaptchaCodeOrGenerateNew}. - * The former method should never change the code (and so return {@code null} for no code) while the latter should - * generate a new code if no code is yet present. If a code is saved, it should never generate a new one. - */ @Test public void shouldHaveSameCodeAfterGeneration() { // given @@ -70,18 +63,14 @@ public class CaptchaManagerTest { CaptchaManager manager = new CaptchaManager(settings); // when - String code1 = manager.getCaptchaCode(player); + String code1 = manager.getCaptchaCodeOrGenerateNew(player); String code2 = manager.getCaptchaCodeOrGenerateNew(player); - String code3 = manager.getCaptchaCode(player); - String code4 = manager.getCaptchaCodeOrGenerateNew(player); - String code5 = manager.getCaptchaCode(player); + String code3 = manager.getCaptchaCodeOrGenerateNew(player); // then - assertThat(code1, nullValue()); - assertThat(code2.length(), equalTo(5)); - assertThat(code3, equalTo(code2)); - assertThat(code4, equalTo(code2)); - assertThat(code5, equalTo(code2)); + assertThat(code1.length(), equalTo(5)); + assertThat(code2, equalTo(code1)); + assertThat(code3, equalTo(code1)); } @Test @@ -104,7 +93,7 @@ public class CaptchaManagerTest { // then 2 assertThat(manager.isCaptchaRequired(player), equalTo(false)); - assertHasCount(manager, player, null); + assertHasCount(manager, player, 0); } @Test @@ -120,7 +109,7 @@ public class CaptchaManagerTest { // then assertThat(manager.isCaptchaRequired(player), equalTo(false)); - assertHasCount(manager, player, null); + assertHasCount(manager, player, 0); } @Test @@ -149,11 +138,12 @@ public class CaptchaManagerTest { given(settings.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(true); given(settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA)).willReturn(maxTries); given(settings.getProperty(SecuritySettings.CAPTCHA_LENGTH)).willReturn(captchaLength); + given(settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET)).willReturn(30); return settings; } private static void assertHasCount(CaptchaManager manager, String player, Integer count) { - Map playerCounts = ReflectionTestUtils + TimedCounter playerCounts = ReflectionTestUtils .getFieldValue(CaptchaManager.class, manager, "playerCounts"); assertThat(playerCounts.get(player.toLowerCase()), equalTo(count)); } From 57ca81f2ba930c5d1819148f337416240fce13b4 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Feb 2017 21:11:57 +0100 Subject: [PATCH 35/79] #1102 commands.yml file should not have any commands by default --- pom.xml | 8 ++++---- src/main/resources/commands.yml | 7 +++---- .../java/tools/filegeneration/GenerateCommandsYml.java | 7 +------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index ff67c0df..8048970b 100644 --- a/pom.xml +++ b/pom.xml @@ -241,8 +241,8 @@ fr.xephi.authme.libs.jalu.injector - com.github.authme.configme - fr.xephi.authme.libs.authme.configme + ch.jalu.configme + fr.xephi.authme.libs.jalu.configme com.zaxxer.hikari @@ -892,7 +892,7 @@ ch.jalu configme - 0.3 + 0.4 compile true @@ -923,7 +923,7 @@ org.mockito mockito-core test - 2.4.1 + 2.7.9 hamcrest-core diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml index f23a68a5..e2001096 100644 --- a/src/main/resources/commands.yml +++ b/src/main/resources/commands.yml @@ -20,7 +20,6 @@ # executor: CONSOLE # # Supported command events: onLogin, onJoin, onRegister -onLogin: - welcome: - command: 'msg %p Welcome back!' - executor: 'PLAYER' +onJoin: {} +onLogin: {} +onRegister: {} diff --git a/src/test/java/tools/filegeneration/GenerateCommandsYml.java b/src/test/java/tools/filegeneration/GenerateCommandsYml.java index b99eef52..73232d90 100644 --- a/src/test/java/tools/filegeneration/GenerateCommandsYml.java +++ b/src/test/java/tools/filegeneration/GenerateCommandsYml.java @@ -2,11 +2,8 @@ package tools.filegeneration; import ch.jalu.configme.SettingsManager; import ch.jalu.configme.resource.YamlFileResource; -import com.google.common.collect.ImmutableMap; -import fr.xephi.authme.settings.commandconfig.Command; import fr.xephi.authme.settings.commandconfig.CommandConfig; import fr.xephi.authme.settings.commandconfig.CommandSettingsHolder; -import fr.xephi.authme.settings.commandconfig.Executor; import tools.utils.AutoToolTask; import tools.utils.ToolsConstants; @@ -23,10 +20,8 @@ public class GenerateCommandsYml implements AutoToolTask { public void executeDefault() { File file = new File(COMMANDS_YML_FILE); - // Get default and add sample entry + // Get the default CommandConfig commandConfig = CommandSettingsHolder.COMMANDS.getDefaultValue(); - commandConfig.setOnLogin( - ImmutableMap.of("welcome", new Command("msg %p Welcome back!", Executor.PLAYER))); // Export the value to the file SettingsManager settingsManager = new SettingsManager( From 18d81868042a8af15f0cd66e9835f7e03741219e Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Feb 2017 21:18:13 +0100 Subject: [PATCH 36/79] #1026 List all available tags for commands.yml in comment --- .../settings/commandconfig/CommandSettingsHolder.java | 7 ++++++- src/main/resources/commands.yml | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java index 11557c09..7104cee2 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java @@ -24,7 +24,12 @@ public final class CommandSettingsHolder implements SettingsHolder { public static Map sectionComments() { String[] comments = { "This configuration file allows you to execute commands on various events.", - "%p in commands will be replaced with the player name.", + "Supported placeholders in commands:", + " %p is replaced with the player name.", + " %nick is replaced with the player's nick name", + " %ip is replaced with the player's IP address", + " %country is replaced with the player's country", + "", "For example, if you want to send a welcome message to a player who just registered:", "onRegister:", " welcome:", diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml index e2001096..e0330bf9 100644 --- a/src/main/resources/commands.yml +++ b/src/main/resources/commands.yml @@ -1,6 +1,11 @@ # This configuration file allows you to execute commands on various events. -# %p in commands will be replaced with the player name. +# Supported placeholders in commands: +# %p is replaced with the player name. +# %nick is replaced with the player's nick name +# %ip is replaced with the player's IP address +# %country is replaced with the player's country +# # For example, if you want to send a welcome message to a player who just registered: # onRegister: # welcome: From 922101d75592c95bc7596c60e1701843c924e2e2 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Feb 2017 22:09:36 +0100 Subject: [PATCH 37/79] #1104 Filter all sensitive command aliases in console filters --- .../xephi/authme/output/LogFilterHelper.java | 14 ++-- .../authme/output/LogFilterHelperTest.java | 80 +++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java diff --git a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java index 605283ac..cf4952a5 100644 --- a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java +++ b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java @@ -1,17 +1,21 @@ package fr.xephi.authme.output; +import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.util.StringUtils; /** * Service class for the log filters. */ -public final class LogFilterHelper { +final class LogFilterHelper { private static final String ISSUED_COMMAND_TEXT = "issued server command:"; - private static final String[] COMMANDS_TO_SKIP = {"/login ", "/l ", "/reg ", "/changepassword ", - "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ", - "/register "}; + @VisibleForTesting + static final String[] COMMANDS_TO_SKIP = { + "/login ", "/l ", "/log ", "/register ", "/reg ", "/unregister ", "/unreg ", + "/changepassword ", "/cp ", "/changepass ", "/authme register ", "/authme reg ", "/authme r ", + "/authme changepassword ", "/authme password ", "/authme changepass ", "/authme cp " + }; private LogFilterHelper() { // Util class @@ -24,7 +28,7 @@ public final class LogFilterHelper { * * @return True if it is a sensitive AuthMe command, false otherwise */ - public static boolean isSensitiveAuthMeCommand(String message) { + static boolean isSensitiveAuthMeCommand(String message) { if (message == null) { return false; } diff --git a/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java new file mode 100644 index 00000000..dae258db --- /dev/null +++ b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java @@ -0,0 +1,80 @@ +package fr.xephi.authme.output; + +import com.google.common.base.Preconditions; +import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandInitializer; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link LogFilterHelper}. + */ +public class LogFilterHelperTest { + + private static final List ALL_COMMANDS = new CommandInitializer().getCommands(); + + /** + * Checks that {@link LogFilterHelper#COMMANDS_TO_SKIP} contains the entries we expect + * (commands with password argument). + */ + @Test + public void shouldBlacklistAllSensitiveCommands() { + // given + List sensitiveCommands = Arrays.asList( + getCommand("register"), getCommand("login"), getCommand("changepassword"), getCommand("unregister"), + getCommand("authme", "register"), getCommand("authme", "changepassword") + ); + // Build array with entries like "/register ", "/authme cp ", "/authme changepass " + String[] expectedEntries = sensitiveCommands.stream() + .map(cmd -> buildCommandSyntaxes(cmd)) + .flatMap(List::stream) + .map(syntax -> syntax + " ") + .toArray(String[]::new); + + // when / then + assertThat(Arrays.asList("test", "toast"), containsInAnyOrder("toast", "test")); + assertThat(Arrays.asList(LogFilterHelper.COMMANDS_TO_SKIP), containsInAnyOrder(expectedEntries)); + + } + + private static CommandDescription getCommand(String label) { + return findCommandWithLabel(label, ALL_COMMANDS); + } + + private static CommandDescription getCommand(String parentLabel, String childLabel) { + CommandDescription parent = getCommand(parentLabel); + return findCommandWithLabel(childLabel, parent.getChildren()); + } + + private static CommandDescription findCommandWithLabel(String label, List commands) { + return commands.stream() + .filter(cmd -> cmd.getLabels().contains(label)) + .findFirst().orElseThrow(() -> new IllegalArgumentException(label)); + } + + /** + * Returns all "command syntaxes" from which the given command can be reached. + * For example, the result might be a List containing "/authme changepassword", "/authme changepass" + * and "/authme cp". + * + * @param command the command to build syntaxes for + * @return command syntaxes + */ + private static List buildCommandSyntaxes(CommandDescription command) { + // assumes that parent can only have one label -> if this fails in the future, we need to revise this method + Preconditions.checkArgument(command.getParent() == null || command.getParent().getLabels().size() == 1); + + String prefix = command.getParent() == null + ? "/" + : "/" + command.getParent().getLabels().get(0) + " "; + return command.getLabels().stream() + .map(label -> prefix + label) + .collect(Collectors.toList()); + } +} From ee51bb3971631935aca95281a1ab86a460b970d6 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Feb 2017 22:20:48 +0100 Subject: [PATCH 38/79] Minor cleanups - Remove forgotten test assertion - Make utils class final - Change RandomString to use char array --- src/main/java/fr/xephi/authme/util/PlayerUtils.java | 2 +- src/main/java/fr/xephi/authme/util/RandomStringUtils.java | 6 +++--- .../java/fr/xephi/authme/output/LogFilterHelperTest.java | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/xephi/authme/util/PlayerUtils.java b/src/main/java/fr/xephi/authme/util/PlayerUtils.java index 7c7302eb..3c4e067b 100644 --- a/src/main/java/fr/xephi/authme/util/PlayerUtils.java +++ b/src/main/java/fr/xephi/authme/util/PlayerUtils.java @@ -6,7 +6,7 @@ import org.bukkit.entity.Player; /** * Player utilities. */ -public class PlayerUtils { +public final class PlayerUtils { // Utility class private PlayerUtils() { diff --git a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java index 0ad582ab..db166a74 100644 --- a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java +++ b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java @@ -8,7 +8,7 @@ import java.util.Random; */ public final class RandomStringUtils { - private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final char[] CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final Random RANDOM = new SecureRandom(); private static final int HEX_MAX_INDEX = 16; private static final int LOWER_ALPHANUMERIC_INDEX = 36; @@ -46,7 +46,7 @@ public final class RandomStringUtils { * @return The random string */ public static String generateLowerUpper(int length) { - return generate(length, CHARS.length()); + return generate(length, CHARS.length); } private static String generate(int length, int maxIndex) { @@ -55,7 +55,7 @@ public final class RandomStringUtils { } StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; ++i) { - sb.append(CHARS.charAt(RANDOM.nextInt(maxIndex))); + sb.append(CHARS[RANDOM.nextInt(maxIndex)]); } return sb.toString(); } diff --git a/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java index dae258db..e0c470b0 100644 --- a/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java +++ b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java @@ -38,7 +38,6 @@ public class LogFilterHelperTest { .toArray(String[]::new); // when / then - assertThat(Arrays.asList("test", "toast"), containsInAnyOrder("toast", "test")); assertThat(Arrays.asList(LogFilterHelper.COMMANDS_TO_SKIP), containsInAnyOrder(expectedEntries)); } From 4edb4e68c212e1f8d5ccd79d6e8c3ca0f8685aa0 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 21 Feb 2017 22:51:45 +0100 Subject: [PATCH 39/79] #1104 Whitelist sensitive commands also when used with "authme:" prefix --- .../xephi/authme/output/LogFilterHelper.java | 18 +++++++++-- .../fr/xephi/authme/util/StringUtils.java | 2 +- .../authme/output/LogFilterHelperTest.java | 31 ++++++++++++------- .../fr/xephi/authme/util/StringUtilsTest.java | 7 +++-- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java index cf4952a5..7d46daf8 100644 --- a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java +++ b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java @@ -3,6 +3,10 @@ package fr.xephi.authme.output; import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.util.StringUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Service class for the log filters. */ @@ -11,11 +15,10 @@ final class LogFilterHelper { private static final String ISSUED_COMMAND_TEXT = "issued server command:"; @VisibleForTesting - static final String[] COMMANDS_TO_SKIP = { + static final List COMMANDS_TO_SKIP = withAndWithoutAuthMePrefix( "/login ", "/l ", "/log ", "/register ", "/reg ", "/unregister ", "/unreg ", "/changepassword ", "/cp ", "/changepass ", "/authme register ", "/authme reg ", "/authme r ", - "/authme changepassword ", "/authme password ", "/authme changepass ", "/authme cp " - }; + "/authme changepassword ", "/authme password ", "/authme changepass ", "/authme cp "); private LogFilterHelper() { // Util class @@ -35,4 +38,13 @@ final class LogFilterHelper { String lowerMessage = message.toLowerCase(); return lowerMessage.contains(ISSUED_COMMAND_TEXT) && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP); } + + private static List withAndWithoutAuthMePrefix(String... commands) { + List commandList = new ArrayList<>(commands.length * 2); + for (String command : commands) { + commandList.add(command); + commandList.add(command.substring(0, 1) + "authme:" + command.substring(1)); + } + return Collections.unmodifiableList(commandList); + } } diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index a4fc733e..5e0696af 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -42,7 +42,7 @@ public final class StringUtils { * * @return True if the string contains at least one of the items */ - public static boolean containsAny(String str, String... pieces) { + public static boolean containsAny(String str, Iterable pieces) { if (str == null) { return false; } diff --git a/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java index e0c470b0..f211a608 100644 --- a/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java +++ b/src/test/java/fr/xephi/authme/output/LogFilterHelperTest.java @@ -1,6 +1,6 @@ package fr.xephi.authme.output; -import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandInitializer; import org.junit.Test; @@ -38,7 +38,7 @@ public class LogFilterHelperTest { .toArray(String[]::new); // when / then - assertThat(Arrays.asList(LogFilterHelper.COMMANDS_TO_SKIP), containsInAnyOrder(expectedEntries)); + assertThat(LogFilterHelper.COMMANDS_TO_SKIP, containsInAnyOrder(expectedEntries)); } @@ -59,21 +59,30 @@ public class LogFilterHelperTest { /** * Returns all "command syntaxes" from which the given command can be reached. - * For example, the result might be a List containing "/authme changepassword", "/authme changepass" - * and "/authme cp". + * For example, the result might be a List containing "/authme changepassword", "/authme changepass", + * "/authme cp", "/authme:authme changepassword" etc. * * @param command the command to build syntaxes for * @return command syntaxes */ private static List buildCommandSyntaxes(CommandDescription command) { - // assumes that parent can only have one label -> if this fails in the future, we need to revise this method - Preconditions.checkArgument(command.getParent() == null || command.getParent().getLabels().size() == 1); + List prefixes = getCommandPrefixes(command); - String prefix = command.getParent() == null - ? "/" - : "/" + command.getParent().getLabels().get(0) + " "; - return command.getLabels().stream() - .map(label -> prefix + label) + return command.getLabels() + .stream() + .map(label -> Lists.transform(prefixes, p -> p + label)) + .flatMap(List::stream) + .collect(Collectors.toList()); + } + + private static List getCommandPrefixes(CommandDescription command) { + if (command.getParent() == null) { + return Arrays.asList("/", "/authme:"); + } + return command.getParent().getLabels() + .stream() + .map(label -> new String[]{"/" + label + " ", "/authme:" + label + " "}) + .flatMap(Arrays::stream) .collect(Collectors.toList()); } } diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 1be95d0d..629adbd4 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.net.MalformedURLException; +import static java.util.Arrays.asList; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertFalse; @@ -23,7 +24,7 @@ public class StringUtilsTest { String piece = "test"; // when - boolean result = StringUtils.containsAny(text, "some", "words", "that", "do not", "exist", piece); + boolean result = StringUtils.containsAny(text, asList("some", "words", "that", "do not", "exist", piece)); // then assertThat(result, equalTo(true)); @@ -35,7 +36,7 @@ public class StringUtilsTest { String text = "This is a test string"; // when - boolean result = StringUtils.containsAny(text, "some", "other", "words", null); + boolean result = StringUtils.containsAny(text, asList("some", "other", "words", null)); // then assertThat(result, equalTo(false)); @@ -44,7 +45,7 @@ public class StringUtilsTest { @Test public void shouldReturnFalseForNullString() { // given/when - boolean result = StringUtils.containsAny(null, "some", "words", "to", "check"); + boolean result = StringUtils.containsAny(null, asList("some", "words", "to", "check")); // then assertThat(result, equalTo(false)); From 72c5cfac688164c97d022b8c7a2040a8802885d7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Feb 2017 17:25:25 +0100 Subject: [PATCH 40/79] Create Duration class and ExpiringSet#getExpiration (prep for #1073) - Move expiring collections to util.expiring package - Change ExpiringSet to remove expired entries during normal calls --- .../fr/xephi/authme/data/CaptchaManager.java | 2 +- .../fr/xephi/authme/data/SessionManager.java | 2 +- .../fr/xephi/authme/data/TempbanManager.java | 2 +- .../authme/service/RecoveryCodeService.java | 2 +- src/main/java/fr/xephi/authme/util/Utils.java | 33 +++++++++ .../xephi/authme/util/expiring/Duration.java | 72 +++++++++++++++++++ .../util/{ => expiring}/ExpiringMap.java | 2 +- .../util/{ => expiring}/ExpiringSet.java | 39 ++++++++-- .../util/{ => expiring}/TimedCounter.java | 2 +- .../xephi/authme/data/CaptchaManagerTest.java | 2 +- .../xephi/authme/data/SessionManagerTest.java | 2 +- .../xephi/authme/data/TempbanManagerTest.java | 2 +- .../service/RecoveryCodeServiceTest.java | 2 +- .../authme/util/expiring/DurationTest.java | 43 +++++++++++ .../util/{ => expiring}/ExpiringMapTest.java | 2 +- .../util/{ => expiring}/ExpiringSetTest.java | 33 ++++++++- .../util/{ => expiring}/TimedCounterTest.java | 2 +- 17 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/util/expiring/Duration.java rename src/main/java/fr/xephi/authme/util/{ => expiring}/ExpiringMap.java (98%) rename src/main/java/fr/xephi/authme/util/{ => expiring}/ExpiringSet.java (65%) rename src/main/java/fr/xephi/authme/util/{ => expiring}/TimedCounter.java (97%) create mode 100644 src/test/java/fr/xephi/authme/util/expiring/DurationTest.java rename src/test/java/fr/xephi/authme/util/{ => expiring}/ExpiringMapTest.java (98%) rename src/test/java/fr/xephi/authme/util/{ => expiring}/ExpiringSetTest.java (68%) rename src/test/java/fr/xephi/authme/util/{ => expiring}/TimedCounterTest.java (97%) diff --git a/src/main/java/fr/xephi/authme/data/CaptchaManager.java b/src/main/java/fr/xephi/authme/data/CaptchaManager.java index 7e83ec23..b328d545 100644 --- a/src/main/java/fr/xephi/authme/data/CaptchaManager.java +++ b/src/main/java/fr/xephi/authme/data/CaptchaManager.java @@ -5,7 +5,7 @@ import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.RandomStringUtils; -import fr.xephi.authme.util.TimedCounter; +import fr.xephi.authme.util.expiring.TimedCounter; import javax.inject.Inject; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/fr/xephi/authme/data/SessionManager.java b/src/main/java/fr/xephi/authme/data/SessionManager.java index 23da3aff..512941db 100644 --- a/src/main/java/fr/xephi/authme/data/SessionManager.java +++ b/src/main/java/fr/xephi/authme/data/SessionManager.java @@ -5,7 +5,7 @@ import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.util.ExpiringSet; +import fr.xephi.authme.util.expiring.ExpiringSet; import javax.inject.Inject; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/fr/xephi/authme/data/TempbanManager.java b/src/main/java/fr/xephi/authme/data/TempbanManager.java index 07019a72..1d55fa8f 100644 --- a/src/main/java/fr/xephi/authme/data/TempbanManager.java +++ b/src/main/java/fr/xephi/authme/data/TempbanManager.java @@ -7,8 +7,8 @@ import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.TimedCounter; import fr.xephi.authme.util.PlayerUtils; +import fr.xephi.authme.util.expiring.TimedCounter; import org.bukkit.entity.Player; import javax.inject.Inject; diff --git a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java index fdc987a2..cae8aaa7 100644 --- a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java +++ b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java @@ -4,8 +4,8 @@ import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.ExpiringMap; import fr.xephi.authme.util.RandomStringUtils; +import fr.xephi.authme.util.expiring.ExpiringMap; import javax.inject.Inject; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index b7628147..e7ec1657 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -3,6 +3,7 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; import java.util.Collection; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** @@ -70,4 +71,36 @@ public final class Utils { return Runtime.getRuntime().availableProcessors(); } + public static Duration convertMillisToSuitableUnit(long duration) { + TimeUnit targetUnit; + if (duration > 1000L * 60L * 60L * 24L) { + targetUnit = TimeUnit.DAYS; + } else if (duration > 1000L * 60L * 60L) { + targetUnit = TimeUnit.HOURS; + } else if (duration > 1000L * 60L) { + targetUnit = TimeUnit.MINUTES; + } else if (duration > 1000L) { + targetUnit = TimeUnit.SECONDS; + } else { + targetUnit = TimeUnit.MILLISECONDS; + } + + return new Duration(targetUnit, duration); + } + + public static final class Duration { + + private final long duration; + private final TimeUnit unit; + + Duration(TimeUnit targetUnit, long durationMillis) { + this(targetUnit, durationMillis, TimeUnit.MILLISECONDS); + } + + Duration(TimeUnit targetUnit, long sourceDuration, TimeUnit sourceUnit) { + this.duration = targetUnit.convert(sourceDuration, sourceUnit); + this.unit = targetUnit; + } + } + } diff --git a/src/main/java/fr/xephi/authme/util/expiring/Duration.java b/src/main/java/fr/xephi/authme/util/expiring/Duration.java new file mode 100644 index 00000000..ecc86299 --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/expiring/Duration.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.util.expiring; + +import java.util.concurrent.TimeUnit; + +/** + * Represents a duration in time, defined by a time unit and a duration. + */ +public class Duration { + + private final long duration; + private final TimeUnit unit; + + /** + * Constructor. + * + * @param duration the duration + * @param unit the time unit in which {@code duration} is expressed + */ + public Duration(long duration, TimeUnit unit) { + this.duration = duration; + this.unit = unit; + } + + /** + * Creates a Duration object for the given duration and unit in the most suitable time unit. + * For example, {@code createWithSuitableUnit(120, TimeUnit.SECONDS)} will return a Duration + * object of 2 minutes. + *

+ * This method only considers the time units days, hours, minutes, and seconds for the objects + * it creates. Conversion is done with {@link TimeUnit#convert} and so always rounds the + * results down. + *

+ * Further examples: + * createWithSuitableUnit(299, TimeUnit.MINUTES); // 4 hours + * createWithSuitableUnit(700, TimeUnit.MILLISECONDS); // 0 seconds + * + * @param sourceDuration the duration + * @param sourceUnit the time unit the duration is expressed in + * @return Duration object using the most suitable time unit + */ + public static Duration createWithSuitableUnit(long sourceDuration, TimeUnit sourceUnit) { + long durationMillis = Math.abs(TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit)); + + TimeUnit targetUnit; + if (durationMillis > 1000L * 60L * 60L * 24L) { + targetUnit = TimeUnit.DAYS; + } else if (durationMillis > 1000L * 60L * 60L) { + targetUnit = TimeUnit.HOURS; + } else if (durationMillis > 1000L * 60L) { + targetUnit = TimeUnit.MINUTES; + } else { + targetUnit = TimeUnit.SECONDS; + } + + long durationInTargetUnit = targetUnit.convert(sourceDuration, sourceUnit); + return new Duration(durationInTargetUnit, targetUnit); + } + + /** + * @return the duration + */ + public long getDuration() { + return duration; + } + + /** + * @return the time unit in which the duration is expressed + */ + public TimeUnit getTimeUnit() { + return unit; + } +} diff --git a/src/main/java/fr/xephi/authme/util/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java similarity index 98% rename from src/main/java/fr/xephi/authme/util/ExpiringMap.java rename to src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java index 519946f8..894b6486 100644 --- a/src/main/java/fr/xephi/authme/util/ExpiringMap.java +++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/fr/xephi/authme/util/ExpiringSet.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java similarity index 65% rename from src/main/java/fr/xephi/authme/util/ExpiringSet.java rename to src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java index 840d5911..7e711673 100644 --- a/src/main/java/fr/xephi/authme/util/ExpiringSet.java +++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -9,9 +9,10 @@ import java.util.concurrent.TimeUnit; * has expired, the set will act as if the entry no longer exists. Time starts * counting after the entry has been inserted. *

- * Internally, expired entries are not cleared automatically. A cleanup can be - * triggered with {@link #removeExpiredEntries()}. Adding an entry that is - * already present effectively resets its expiration. + * Internally, expired entries are not guaranteed to be cleared automatically. + * A cleanup of all expired entries may be triggered with + * {@link #removeExpiredEntries()}. Adding an entry that is already present + * effectively resets its expiration. * * @param the type of the entries */ @@ -47,7 +48,14 @@ public class ExpiringSet { */ public boolean contains(E entry) { Long expiration = entries.get(entry); - return expiration != null && expiration > System.currentTimeMillis(); + if (expiration == null) { + return false; + } else if (expiration > System.currentTimeMillis()) { + return true; + } else { + entries.remove(entry); + return false; + } } /** @@ -73,6 +81,27 @@ public class ExpiringSet { entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue()); } + /** + * Returns the duration of the entry until it expires (provided it is not removed or re-added). + * If the entry does not exist, -1 is returned. + * + * @param entry the entry whose duration before it expires should be returned + * @param unit the unit in which to return the duration + * @return duration the entry will remain in the set (if there are not modifications) + */ + public long getExpiration(E entry, TimeUnit unit) { + Long expiration = entries.get(entry); + if (expiration == null) { + return -1; + } + long stillPresentMillis = expiration - System.currentTimeMillis(); + if (stillPresentMillis < 0) { + entries.remove(entry); + return -1; + } + return unit.convert(stillPresentMillis, TimeUnit.MILLISECONDS); + } + /** * Sets a new expiration duration. Note that already present entries * will still make use of the old expiration. diff --git a/src/main/java/fr/xephi/authme/util/TimedCounter.java b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java similarity index 97% rename from src/main/java/fr/xephi/authme/util/TimedCounter.java rename to src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java index 59c54d4d..67839294 100644 --- a/src/main/java/fr/xephi/authme/util/TimedCounter.java +++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import java.util.Objects; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java b/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java index d53091db..6f20d830 100644 --- a/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/CaptchaManagerTest.java @@ -3,7 +3,7 @@ package fr.xephi.authme.data; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.TimedCounter; +import fr.xephi.authme.util.expiring.TimedCounter; import org.junit.Test; import static org.hamcrest.Matchers.equalTo; diff --git a/src/test/java/fr/xephi/authme/data/SessionManagerTest.java b/src/test/java/fr/xephi/authme/data/SessionManagerTest.java index 50b18178..738c1c15 100644 --- a/src/test/java/fr/xephi/authme/data/SessionManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/SessionManagerTest.java @@ -3,7 +3,7 @@ package fr.xephi.authme.data; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.util.ExpiringSet; +import fr.xephi.authme.util.expiring.ExpiringSet; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; diff --git a/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java b/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java index 4fd8ad11..ab1cbb0b 100644 --- a/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/TempbanManagerTest.java @@ -7,7 +7,7 @@ import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.TimedCounter; +import fr.xephi.authme.util.expiring.TimedCounter; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java index 576c5da3..eb8af0dc 100644 --- a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java @@ -6,7 +6,7 @@ import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.ExpiringMap; +import fr.xephi.authme.util.expiring.ExpiringMap; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/src/test/java/fr/xephi/authme/util/expiring/DurationTest.java b/src/test/java/fr/xephi/authme/util/expiring/DurationTest.java new file mode 100644 index 00000000..03a0f9d4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/expiring/DurationTest.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.util.expiring; + +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link Duration}. + */ +public class DurationTest { + + @Test + public void shouldConvertToAppropriateTimeUnit() { + check(Duration.createWithSuitableUnit(0, TimeUnit.HOURS), + 0, TimeUnit.SECONDS); + + check(Duration.createWithSuitableUnit(124, TimeUnit.MINUTES), + 2, TimeUnit.HOURS); + + check(Duration.createWithSuitableUnit(300, TimeUnit.HOURS), + 12, TimeUnit.DAYS); + + check(Duration.createWithSuitableUnit(60 * 24 * 50 + 8, TimeUnit.MINUTES), + 50, TimeUnit.DAYS); + + check(Duration.createWithSuitableUnit(1000L * 60 * 60 * 24 * 7 + 3000, TimeUnit.MILLISECONDS), + 7, TimeUnit.DAYS); + + check(Duration.createWithSuitableUnit(1000L * 60 * 60 * 3 + 1400, TimeUnit.MILLISECONDS), + 3, TimeUnit.HOURS); + + check(Duration.createWithSuitableUnit(248, TimeUnit.SECONDS), + 4, TimeUnit.MINUTES); + } + + private static void check(Duration duration, long expectedDuration, TimeUnit expectedUnit) { + assertThat(duration.getTimeUnit(), equalTo(expectedUnit)); + assertThat(duration.getDuration(), equalTo(expectedDuration)); + } +} diff --git a/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java b/src/test/java/fr/xephi/authme/util/expiring/ExpiringMapTest.java similarity index 98% rename from src/test/java/fr/xephi/authme/util/ExpiringMapTest.java rename to src/test/java/fr/xephi/authme/util/expiring/ExpiringMapTest.java index cdc9c36a..cdace306 100644 --- a/src/test/java/fr/xephi/authme/util/ExpiringMapTest.java +++ b/src/test/java/fr/xephi/authme/util/expiring/ExpiringMapTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import org.junit.Test; diff --git a/src/test/java/fr/xephi/authme/util/ExpiringSetTest.java b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java similarity index 68% rename from src/test/java/fr/xephi/authme/util/ExpiringSetTest.java rename to src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java index f58e0312..15a55aa0 100644 --- a/src/test/java/fr/xephi/authme/util/ExpiringSetTest.java +++ b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java @@ -1,9 +1,10 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import org.junit.Test; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @@ -88,4 +89,34 @@ public class ExpiringSetTest { assertThat(set.contains(3), equalTo(false)); assertThat(set.contains(6), equalTo(true)); } + + @Test + public void shouldReturnExpiration() { + // given + ExpiringSet set = new ExpiringSet<>(123, TimeUnit.MINUTES); + set.add("my entry"); + + // when + long expiresInHours = set.getExpiration("my entry", TimeUnit.HOURS); + long expiresInMinutes = set.getExpiration("my entry", TimeUnit.MINUTES); + long unknownExpires = set.getExpiration("bogus", TimeUnit.SECONDS); + + // then + assertThat(expiresInHours, equalTo(2L)); + assertThat(expiresInMinutes, either(equalTo(122L)).or(equalTo(123L))); + assertThat(unknownExpires, equalTo(-1L)); + } + + @Test + public void shouldReturnMinusOneForExpiredEntry() { + // given + ExpiringSet set = new ExpiringSet<>(-100, TimeUnit.SECONDS); + set.add(23); + + // when + long expiresInSeconds = set.getExpiration(23, TimeUnit.SECONDS); + + // then + assertThat(expiresInSeconds, equalTo(-1L)); + } } diff --git a/src/test/java/fr/xephi/authme/util/TimedCounterTest.java b/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java similarity index 97% rename from src/test/java/fr/xephi/authme/util/TimedCounterTest.java rename to src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java index 9903842d..dcfcb73a 100644 --- a/src/test/java/fr/xephi/authme/util/TimedCounterTest.java +++ b/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.util; +package fr.xephi.authme.util.expiring; import org.junit.Test; From a4b440bcca2536b3715f0bcfe54f4a7f558e6b13 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Feb 2017 20:14:58 +0100 Subject: [PATCH 41/79] Separate email preparation and email sending into separate classes - SendMailSSL keeps on handling the technical details for sending mails, while EmailService offers methods to other classes and worries about generating the correct email content --- .../authme/debug/TestEmailSender.java | 8 +- .../executable/email/RecoverEmailCommand.java | 10 +- .../executable/register/RegisterCommand.java | 6 +- .../fr/xephi/authme/mail/EmailService.java | 140 +++++++++++++ .../fr/xephi/authme/mail/SendMailSSL.java | 120 +---------- .../EmailRegisterExecutorProvider.java | 8 +- .../email/RecoverEmailCommandTest.java | 52 ++--- .../register/RegisterCommandTest.java | 30 +-- .../xephi/authme/mail/EmailServiceTest.java | 190 ++++++++++++++++++ .../fr/xephi/authme/mail/SendMailSSLTest.java | 138 ------------- .../EmailRegisterExecutorProviderTest.java | 12 +- 11 files changed, 396 insertions(+), 318 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/mail/EmailService.java create mode 100644 src/test/java/fr/xephi/authme/mail/EmailServiceTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java index cfad4013..2a994246 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -2,7 +2,7 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -18,7 +18,7 @@ class TestEmailSender implements DebugSection { private DataSource dataSource; @Inject - private SendMailSSL sendMailSSL; + private EmailService emailService; @Override @@ -33,7 +33,7 @@ class TestEmailSender implements DebugSection { @Override public void execute(CommandSender sender, List arguments) { - if (!sendMailSSL.hasAllInformation()) { + if (!emailService.hasAllInformation()) { sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + "for sending emails. Please check your config.yml"); return; @@ -43,7 +43,7 @@ class TestEmailSender implements DebugSection { // getEmail() takes care of informing the sender of the error if email == null if (email != null) { - boolean sendMail = sendMailSSL.sendTestEmail(email); + boolean sendMail = emailService.sendTestEmail(email); if (sendMail) { sender.sendMessage("Test email sent to " + email + " with success"); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 5dc16ac6..eb188f79 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -5,7 +5,7 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; @@ -37,7 +37,7 @@ public class RecoverEmailCommand extends PlayerCommand { private PlayerCache playerCache; @Inject - private SendMailSSL sendMailSsl; + private EmailService emailService; @Inject private RecoveryCodeService recoveryCodeService; @@ -47,7 +47,7 @@ public class RecoverEmailCommand extends PlayerCommand { final String playerMail = arguments.get(0); final String playerName = player.getName(); - if (!sendMailSsl.hasAllInformation()) { + if (!emailService.hasAllInformation()) { ConsoleLogger.warning("Mail API is not set"); commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); return; @@ -84,7 +84,7 @@ public class RecoverEmailCommand extends PlayerCommand { private void createAndSendRecoveryCode(Player player, String email) { String recoveryCode = recoveryCodeService.generateCode(player.getName()); - boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode); + boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode); if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_CODE_SENT); } else { @@ -108,7 +108,7 @@ public class RecoverEmailCommand extends PlayerCommand { HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); dataSource.updatePassword(name, hashNew); - boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass); + boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass); if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } else { diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java index ac4bb4bd..6a0a590c 100644 --- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java @@ -2,7 +2,7 @@ package fr.xephi.authme.command.executable.register; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; @@ -37,7 +37,7 @@ public class RegisterCommand extends PlayerCommand { private CommonService commonService; @Inject - private SendMailSSL sendMailSsl; + private EmailService emailService; @Inject private ValidationService validationService; @@ -127,7 +127,7 @@ public class RegisterCommand extends PlayerCommand { } private void handleEmailRegistration(Player player, List arguments) { - if (!sendMailSsl.hasAllInformation()) { + if (!emailService.hasAllInformation()) { commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); ConsoleLogger.warning("Cannot register player '" + player.getName() + "': no email or password is set " + "to send emails from. Please adjust your config at " + EmailSettings.MAIL_ACCOUNT.getPath()); diff --git a/src/main/java/fr/xephi/authme/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java new file mode 100644 index 00000000..46343369 --- /dev/null +++ b/src/main/java/fr/xephi/authme/mail/EmailService.java @@ -0,0 +1,140 @@ +package fr.xephi.authme.mail; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.FileUtils; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; +import org.bukkit.Server; + +import javax.activation.DataSource; +import javax.activation.FileDataSource; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; + +/** + * Creates emails and sends them. + */ +public class EmailService { + + private final File dataFolder; + private final String serverName; + private final Settings settings; + private final SendMailSSL sendMailSSL; + + @Inject + EmailService(@DataFolder File dataFolder, Server server, Settings settings, SendMailSSL sendMailSSL) { + this.dataFolder = dataFolder; + this.serverName = server.getServerName(); + this.settings = settings; + this.sendMailSSL = sendMailSSL; + } + + public boolean hasAllInformation() { + return sendMailSSL.hasAllInformation(); + } + + + /** + * Sends an email to the user with his new password. + * + * @param name the name of the player + * @param mailAddress the player's email + * @param newPass the new password + * @return true if email could be sent, false otherwise + */ + public boolean sendPasswordMail(String name, String mailAddress, String newPass) { + if (!hasAllInformation()) { + ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete"); + return false; + } + + HtmlEmail email; + try { + email = sendMailSSL.initializeMail(mailAddress); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email with the given settings:", e); + return false; + } + + String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass); + // Generate an image? + File file = null; + if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { + try { + file = generateImage(name, newPass); + mailText = embedImageIntoEmailContent(file, email, mailText); + } catch (IOException | EmailException e) { + ConsoleLogger.logException( + "Unable to send new password as image for email " + mailAddress + ":", e); + } + } + + boolean couldSendEmail = sendMailSSL.sendEmail(mailText, email); + FileUtils.delete(file); + return couldSendEmail; + } + + public boolean sendRecoveryCode(String name, String email, String code) { + HtmlEmail htmlEmail; + try { + htmlEmail = sendMailSSL.initializeMail(email); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email for recovery code:", e); + return false; + } + + String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), + name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); + return sendMailSSL.sendEmail(message, htmlEmail); + } + + public boolean sendTestEmail(String email) { + HtmlEmail htmlEmail; + try { + htmlEmail = sendMailSSL.initializeMail(email); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email for sample email:", e); + return false; + } + + htmlEmail.setSubject("AuthMe test email"); + String message = "Hello there!
This is a sample email sent to you from a Minecraft server (" + + serverName + ") via /authme debug mail. If you're seeing this, sending emails should be fine."; + return sendMailSSL.sendEmail(message, htmlEmail); + } + + private File generateImage(String name, String newPass) throws IOException { + ImageGenerator gen = new ImageGenerator(newPass); + File file = new File(dataFolder, name + "_new_pass.jpg"); + ImageIO.write(gen.generateImage(), "jpg", file); + return file; + } + + private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content) + throws EmailException { + DataSource source = new FileDataSource(image); + String tag = email.embed(source, image.getName()); + return content.replace("", ""); + } + + private String replaceTagsForPasswordMail(String mailText, String name, String newPass) { + return mailText + .replace("", name) + .replace("", serverName) + .replace("", newPass); + } + + private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) { + return mailText + .replace("", name) + .replace("", serverName) + .replace("", code) + .replace("", String.valueOf(hoursValid)); + } +} diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index f5952e40..2b482b26 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -1,27 +1,17 @@ package fr.xephi.authme.mail; -import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; -import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.StringUtils; import org.apache.commons.mail.EmailConstants; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; -import org.bukkit.Server; import javax.activation.CommandMap; -import javax.activation.DataSource; -import javax.activation.FileDataSource; import javax.activation.MailcapCommandMap; -import javax.imageio.ImageIO; import javax.inject.Inject; import javax.mail.Session; -import java.io.File; -import java.io.IOException; import java.security.Security; import java.util.Properties; @@ -34,14 +24,10 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; */ public class SendMailSSL { - private final File dataFolder; - private final String serverName; private final Settings settings; @Inject - SendMailSSL(@DataFolder File dataFolder, Server server, Settings settings) { - this.dataFolder = dataFolder; - this.serverName = server.getServerName(); + SendMailSSL(Settings settings) { this.settings = settings; } @@ -55,91 +41,7 @@ public class SendMailSSL { && !settings.getProperty(MAIL_PASSWORD).isEmpty(); } - /** - * Sends an email to the user with his new password. - * - * @param name the name of the player - * @param mailAddress the player's email - * @param newPass the new password - * @return true if email could be sent, false otherwise - */ - public boolean sendPasswordMail(String name, String mailAddress, String newPass) { - if (!hasAllInformation()) { - ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete"); - return false; - } - - HtmlEmail email; - try { - email = initializeMail(mailAddress); - } catch (EmailException e) { - ConsoleLogger.logException("Failed to create email with the given settings:", e); - return false; - } - - String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass); - // Generate an image? - File file = null; - if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) { - try { - file = generateImage(name, newPass); - mailText = embedImageIntoEmailContent(file, email, mailText); - } catch (IOException | EmailException e) { - ConsoleLogger.logException( - "Unable to send new password as image for email " + mailAddress + ":", e); - } - } - - boolean couldSendEmail = sendEmail(mailText, email); - FileUtils.delete(file); - return couldSendEmail; - } - - public boolean sendRecoveryCode(String name, String email, String code) { - HtmlEmail htmlEmail; - try { - htmlEmail = initializeMail(email); - } catch (EmailException e) { - ConsoleLogger.logException("Failed to create email for recovery code:", e); - return false; - } - - String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), - name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); - return sendEmail(message, htmlEmail); - } - - public boolean sendTestEmail(String email) { - HtmlEmail htmlEmail; - try { - htmlEmail = initializeMail(email); - } catch (EmailException e) { - ConsoleLogger.logException("Failed to create email for sample email:", e); - return false; - } - - htmlEmail.setSubject("AuthMe test email"); - String message = "Hello there!
This is a sample email sent to you from a Minecraft server (" - + serverName + ") via /authme debug mail. If you're seeing this, sending emails should be fine."; - return sendEmail(message, htmlEmail); - } - - private File generateImage(String name, String newPass) throws IOException { - ImageGenerator gen = new ImageGenerator(newPass); - File file = new File(dataFolder, name + "_new_pass.jpg"); - ImageIO.write(gen.generateImage(), "jpg", file); - return file; - } - - private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content) - throws EmailException { - DataSource source = new FileDataSource(image); - String tag = email.embed(source, image.getName()); - return content.replace("", ""); - } - - @VisibleForTesting - HtmlEmail initializeMail(String emailAddress) throws EmailException { + public HtmlEmail initializeMail(String emailAddress) throws EmailException { String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS)) ? settings.getProperty(EmailSettings.MAIL_ACCOUNT) : settings.getProperty(EmailSettings.MAIL_ADDRESS); @@ -163,8 +65,7 @@ public class SendMailSSL { return email; } - @VisibleForTesting - boolean sendEmail(String content, HtmlEmail email) { + public boolean sendEmail(String content, HtmlEmail email) { Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader()); // Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative // cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type @@ -191,21 +92,6 @@ public class SendMailSSL { } } - private String replaceTagsForPasswordMail(String mailText, String name, String newPass) { - return mailText - .replace("", name) - .replace("", serverName) - .replace("", newPass); - } - - private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) { - return mailText - .replace("", name) - .replace("", serverName) - .replace("", code) - .replace("", String.valueOf(hoursValid)); - } - private void setPropertiesForPort(HtmlEmail email, int port) throws EmailException { switch (port) { case 587: diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java index 88f8cd60..7a6e702d 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java @@ -2,7 +2,7 @@ package fr.xephi.authme.process.register.executors; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.SyncProcessManager; @@ -15,8 +15,8 @@ import org.bukkit.entity.Player; import javax.inject.Inject; -import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; /** @@ -34,7 +34,7 @@ class EmailRegisterExecutorProvider { private CommonService commonService; @Inject - private SendMailSSL sendMailSsl; + private EmailService emailService; @Inject private SyncProcessManager syncProcessManager; @@ -80,7 +80,7 @@ class EmailRegisterExecutorProvider { @Override public void executePostPersistAction() { - boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password); + boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password); if (couldSendMail) { syncProcessManager.processSyncEmailRegister(player); } else { diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java index 27166f95..556a1033 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -4,7 +4,7 @@ import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; @@ -59,7 +59,7 @@ public class RecoverEmailCommandTest { private PlayerCache playerCache; @Mock - private SendMailSSL sendMailSsl; + private EmailService emailService; @Mock private RecoveryCodeService recoveryCodeService; @@ -72,7 +72,7 @@ public class RecoverEmailCommandTest { @Test public void shouldHandleMissingMailProperties() { // given - given(sendMailSsl.hasAllInformation()).willReturn(false); + given(emailService.hasAllInformation()).willReturn(false); Player sender = mock(Player.class); // when @@ -89,14 +89,14 @@ public class RecoverEmailCommandTest { String name = "Bobby"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(true); // when command.executeCommand(sender, Collections.singletonList("bobby@example.org")); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verifyZeroInteractions(dataSource); verify(commandService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR); } @@ -107,7 +107,7 @@ public class RecoverEmailCommandTest { String name = "Player123"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); given(dataSource.getAuth(name)).willReturn(null); @@ -115,7 +115,7 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList("someone@example.com")); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); verify(commandService).send(sender, MessageKey.USAGE_REGISTER); @@ -127,7 +127,7 @@ public class RecoverEmailCommandTest { String name = "Tract0r"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(DEFAULT_EMAIL)); @@ -135,7 +135,7 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList(DEFAULT_EMAIL)); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); verify(commandService).send(sender, MessageKey.INVALID_EMAIL); @@ -147,7 +147,7 @@ public class RecoverEmailCommandTest { String name = "Rapt0r"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); given(dataSource.getAuth(name)).willReturn(newAuthWithEmail("raptor@example.org")); @@ -155,7 +155,7 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList("wrong-email@example.com")); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); verify(commandService).send(sender, MessageKey.INVALID_EMAIL); @@ -167,8 +167,8 @@ public class RecoverEmailCommandTest { String name = "Vultur3"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); - given(sendMailSsl.sendRecoveryCode(anyString(), anyString(), anyString())).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); + given(emailService.sendRecoveryCode(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "v@example.com"; given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(email)); @@ -180,11 +180,11 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList(email.toUpperCase())); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verify(recoveryCodeService).generateCode(name); verify(commandService).send(sender, MessageKey.RECOVERY_CODE_SENT); - verify(sendMailSsl).sendRecoveryCode(name, email, code); + verify(emailService).sendRecoveryCode(name, email, code); } @Test @@ -193,7 +193,7 @@ public class RecoverEmailCommandTest { String name = "Vultur3"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "vulture@example.com"; PlayerAuth auth = newAuthWithEmail(email); @@ -205,10 +205,10 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Arrays.asList(email, "bogus")); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource, only()).getAuth(name); verify(commandService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE); - verifyNoMoreInteractions(sendMailSsl); + verifyNoMoreInteractions(emailService); } @Test @@ -217,8 +217,8 @@ public class RecoverEmailCommandTest { String name = "Vultur3"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); - given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); + given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "vulture@example.com"; String code = "A6EF3AC8"; @@ -234,7 +234,7 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Arrays.asList(email, code)); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name)); @@ -242,7 +242,7 @@ public class RecoverEmailCommandTest { assertThat(generatedPassword, stringWithLength(20)); verify(dataSource).updatePassword(eq(name), any(HashedPassword.class)); verify(recoveryCodeService).removeCode(name); - verify(sendMailSsl).sendPasswordMail(name, email, generatedPassword); + verify(emailService).sendPasswordMail(name, email, generatedPassword); verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } @@ -252,8 +252,8 @@ public class RecoverEmailCommandTest { String name = "sh4rK"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(sendMailSsl.hasAllInformation()).willReturn(true); - given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); + given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); String email = "shark@example.org"; PlayerAuth auth = newAuthWithEmail(email); @@ -267,14 +267,14 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList(email)); // then - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); verify(passwordSecurity).computeHash(passwordCaptor.capture(), eq(name)); String generatedPassword = passwordCaptor.getValue(); assertThat(generatedPassword, stringWithLength(20)); verify(dataSource).updatePassword(eq(name), any(HashedPassword.class)); - verify(sendMailSsl).sendPasswordMail(name, email, generatedPassword); + verify(emailService).sendPasswordMail(name, email, generatedPassword); verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java index 76d1352d..c87252e1 100644 --- a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -1,7 +1,7 @@ package fr.xephi.authme.command.executable.register; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; @@ -52,7 +52,7 @@ public class RegisterCommandTest { private Management management; @Mock - private SendMailSSL sendMailSsl; + private EmailService emailService; @Mock private ValidationService validationService; @@ -82,7 +82,7 @@ public class RegisterCommandTest { // then verify(sender).sendMessage(argThat(containsString("Player only!"))); - verifyZeroInteractions(management, sendMailSsl); + verifyZeroInteractions(management, emailService); } @Test @@ -98,7 +98,7 @@ public class RegisterCommandTest { // then verify(management).performRegister(player, executor); - verifyZeroInteractions(sendMailSsl); + verifyZeroInteractions(emailService); } @Test @@ -111,7 +111,7 @@ public class RegisterCommandTest { // then verify(commonService).send(player, MessageKey.USAGE_REGISTER); - verifyZeroInteractions(management, sendMailSsl); + verifyZeroInteractions(management, emailService); } @Test @@ -126,13 +126,13 @@ public class RegisterCommandTest { // then verify(commonService).send(player, MessageKey.USAGE_REGISTER); - verifyZeroInteractions(management, sendMailSsl); + verifyZeroInteractions(management, emailService); } @Test public void shouldReturnErrorForMissingEmailConfirmation() { // given - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.EMAIL); given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.EMAIL_MANDATORY); given(validationService.validateEmail(anyString())).willReturn(true); @@ -150,7 +150,7 @@ public class RegisterCommandTest { public void shouldThrowErrorForMissingEmailConfiguration() { // given given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.EMAIL); - given(sendMailSsl.hasAllInformation()).willReturn(false); + given(emailService.hasAllInformation()).willReturn(false); Player player = mock(Player.class); // when @@ -158,7 +158,7 @@ public class RegisterCommandTest { // then verify(commonService).send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verifyZeroInteractions(management); } @@ -168,7 +168,7 @@ public class RegisterCommandTest { String playerMail = "player@example.org"; given(validationService.validateEmail(playerMail)).willReturn(false); given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.EMAIL); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); Player player = mock(Player.class); // when @@ -187,7 +187,7 @@ public class RegisterCommandTest { given(validationService.validateEmail(playerMail)).willReturn(true); given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.EMAIL); given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.CONFIRMATION); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); Player player = mock(Player.class); // when @@ -195,7 +195,7 @@ public class RegisterCommandTest { // then verify(commonService).send(player, MessageKey.USAGE_REGISTER); - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verifyZeroInteractions(management); } @@ -206,7 +206,7 @@ public class RegisterCommandTest { given(validationService.validateEmail(playerMail)).willReturn(true); given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.EMAIL); given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.CONFIRMATION); - given(sendMailSsl.hasAllInformation()).willReturn(true); + given(emailService.hasAllInformation()).willReturn(true); Player player = mock(Player.class); RegistrationExecutor executor = mock(RegistrationExecutor.class); given(registrationExecutorProvider.getEmailRegisterExecutor(player, playerMail)).willReturn(executor); @@ -216,7 +216,7 @@ public class RegisterCommandTest { // then verify(validationService).validateEmail(playerMail); - verify(sendMailSsl).hasAllInformation(); + verify(emailService).hasAllInformation(); verify(management).performRegister(player, executor); } @@ -232,7 +232,7 @@ public class RegisterCommandTest { // then verify(commonService).send(player, MessageKey.PASSWORD_MATCH_ERROR); - verifyZeroInteractions(management, sendMailSsl); + verifyZeroInteractions(management, emailService); } @Test diff --git a/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java b/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java new file mode 100644 index 00000000..9feb9423 --- /dev/null +++ b/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java @@ -0,0 +1,190 @@ +package fr.xephi.authme.mail; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; +import org.bukkit.Server; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; + +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.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link EmailService}. + */ +@RunWith(DelayedInjectionRunner.class) +public class EmailServiceTest { + + @InjectDelayed + private EmailService emailService; + + @Mock + private Settings settings; + @Mock + private Server server; + @Mock + private SendMailSSL sendMailSSL; + @DataFolder + private File dataFolder; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + public void initFields() throws IOException { + dataFolder = temporaryFolder.newFolder(); + given(server.getServerName()).willReturn("serverName"); + given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org"); + given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234"); + given(sendMailSSL.hasAllInformation()).willReturn(true); + } + + @Test + public void shouldHaveAllInformation() { + // given / when / then + assertThat(emailService.hasAllInformation(), equalTo(true)); + } + + @Test + public void shouldSendPasswordMail() throws EmailException { + // given + given(settings.getPasswordEmailMessage()) + .willReturn("Hi , your new password for is "); + given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); + HtmlEmail email = mock(HtmlEmail.class); + given(sendMailSSL.initializeMail(anyString())).willReturn(email); + given(sendMailSSL.sendEmail(anyString(), eq(email))).willReturn(true); + + // when + boolean result = emailService.sendPasswordMail("Player", "user@example.com", "new_password"); + + // then + assertThat(result, equalTo(true)); + verify(sendMailSSL).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), + equalTo("Hi Player, your new password for serverName is new_password")); + } + + @Test + public void shouldHandleMailCreationError() throws EmailException { + // given + doThrow(EmailException.class).when(sendMailSSL).initializeMail(anyString()); + + // when + boolean result = emailService.sendPasswordMail("Player", "user@example.com", "new_password"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSSL).initializeMail("user@example.com"); + verify(sendMailSSL, never()).sendEmail(anyString(), any(HtmlEmail.class)); + } + + @Test + public void shouldHandleMailSendingFailure() throws EmailException { + // given + given(settings.getPasswordEmailMessage()).willReturn("Hi , your new pass is "); + given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); + HtmlEmail email = mock(HtmlEmail.class); + given(sendMailSSL.initializeMail(anyString())).willReturn(email); + given(sendMailSSL.sendEmail(anyString(), any(HtmlEmail.class))).willReturn(false); + + // when + boolean result = emailService.sendPasswordMail("bobby", "user@example.com", "myPassw0rd"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSSL).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi bobby, your new pass is myPassw0rd")); + } + + @Test + public void shouldSendRecoveryCode() throws EmailException { + // given + given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); + given(settings.getRecoveryCodeEmailMessage()) + .willReturn("Hi , your code on is (valid hours)"); + HtmlEmail email = mock(HtmlEmail.class); + given(sendMailSSL.initializeMail(anyString())).willReturn(email); + given(sendMailSSL.sendEmail(anyString(), any(HtmlEmail.class))).willReturn(true); + + // when + boolean result = emailService.sendRecoveryCode("Timmy", "tim@example.com", "12C56A"); + + // then + assertThat(result, equalTo(true)); + verify(sendMailSSL).initializeMail("tim@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi Timmy, your code on serverName is 12C56A (valid 7 hours)")); + } + + @Test + public void shouldHandleMailCreationErrorForRecoveryCode() throws EmailException { + // given + given(sendMailSSL.initializeMail(anyString())).willThrow(EmailException.class); + + // when + boolean result = emailService.sendRecoveryCode("Player", "player@example.org", "ABC1234"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSSL).initializeMail("player@example.org"); + verify(sendMailSSL, never()).sendEmail(anyString(), any(HtmlEmail.class)); + } + + @Test + public void shouldHandleFailureToSendRecoveryCode() throws EmailException { + // given + given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); + given(settings.getRecoveryCodeEmailMessage()).willReturn("Hi , your code is "); + EmailService sendMailSpy = spy(emailService); + HtmlEmail email = mock(HtmlEmail.class); + given(sendMailSSL.initializeMail(anyString())).willReturn(email); + given(sendMailSSL.sendEmail(anyString(), any(HtmlEmail.class))).willReturn(false); + + // when + boolean result = sendMailSpy.sendRecoveryCode("John", "user@example.com", "1DEF77"); + + // then + assertThat(result, equalTo(false)); + verify(sendMailSSL).initializeMail("user@example.com"); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + assertThat(messageCaptor.getValue(), equalTo("Hi John, your code is 1DEF77")); + } + +} diff --git a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java index 027aea57..835d4b49 100644 --- a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java +++ b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java @@ -4,22 +4,17 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.settings.properties.SecuritySettings; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; -import org.bukkit.Server; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import java.io.File; import java.io.IOException; import java.util.Properties; @@ -28,16 +23,7 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; 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.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; /** * Test for {@link SendMailSSL}. @@ -50,10 +36,6 @@ public class SendMailSSLTest { @Mock private Settings settings; - @Mock - private Server server; - @DataFolder - private File dataFolder; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -65,8 +47,6 @@ public class SendMailSSLTest { @BeforeInjecting public void initFields() throws IOException { - dataFolder = temporaryFolder.newFolder(); - given(server.getServerName()).willReturn("serverName"); given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org"); given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234"); } @@ -77,123 +57,6 @@ public class SendMailSSLTest { assertThat(sendMailSSL.hasAllInformation(), equalTo(true)); } - @Test - public void shouldSendPasswordMail() throws EmailException { - // given - given(settings.getPasswordEmailMessage()) - .willReturn("Hi , your new password for is "); - given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); - SendMailSSL sendMailSpy = spy(sendMailSSL); - HtmlEmail email = mock(HtmlEmail.class); - doReturn(email).when(sendMailSpy).initializeMail(anyString()); - doReturn(true).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); - - // when - boolean result = sendMailSpy.sendPasswordMail("Player", "user@example.com", "new_password"); - - // then - assertThat(result, equalTo(true)); - verify(sendMailSpy).initializeMail("user@example.com"); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); - assertThat(messageCaptor.getValue(), - equalTo("Hi Player, your new password for serverName is new_password")); - } - - @Test - public void shouldHandleMailCreationError() throws EmailException { - // given - SendMailSSL sendMailSpy = spy(sendMailSSL); - doThrow(EmailException.class).when(sendMailSpy).initializeMail(anyString()); - - // when - boolean result = sendMailSpy.sendPasswordMail("Player", "user@example.com", "new_password"); - - // then - assertThat(result, equalTo(false)); - verify(sendMailSpy).initializeMail("user@example.com"); - verify(sendMailSpy, never()).sendEmail(anyString(), any(HtmlEmail.class)); - } - - @Test - public void shouldHandleMailSendingFailure() throws EmailException { - // given - given(settings.getPasswordEmailMessage()).willReturn("Hi , your new pass is "); - given(settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)).willReturn(false); - SendMailSSL sendMailSpy = spy(sendMailSSL); - HtmlEmail email = mock(HtmlEmail.class); - doReturn(email).when(sendMailSpy).initializeMail(anyString()); - doReturn(false).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); - - // when - boolean result = sendMailSpy.sendPasswordMail("bobby", "user@example.com", "myPassw0rd"); - - // then - assertThat(result, equalTo(false)); - verify(sendMailSpy).initializeMail("user@example.com"); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); - assertThat(messageCaptor.getValue(), equalTo("Hi bobby, your new pass is myPassw0rd")); - } - - @Test - public void shouldSendRecoveryCode() throws EmailException { - // given - given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); - given(settings.getRecoveryCodeEmailMessage()) - .willReturn("Hi , your code on is (valid hours)"); - SendMailSSL sendMailSpy = spy(sendMailSSL); - HtmlEmail email = mock(HtmlEmail.class); - doReturn(email).when(sendMailSpy).initializeMail(anyString()); - doReturn(true).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); - - // when - boolean result = sendMailSpy.sendRecoveryCode("Timmy", "tim@example.com", "12C56A"); - - // then - assertThat(result, equalTo(true)); - verify(sendMailSpy).initializeMail("tim@example.com"); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); - assertThat(messageCaptor.getValue(), equalTo("Hi Timmy, your code on serverName is 12C56A (valid 7 hours)")); - } - - @Test - public void shouldHandleMailCreationErrorForRecoveryCode() throws EmailException { - // given - SendMailSSL sendMailSpy = spy(sendMailSSL); - doThrow(EmailException.class).when(sendMailSpy).initializeMail(anyString()); - - // when - boolean result = sendMailSpy.sendRecoveryCode("Player", "player@example.org", "ABC1234"); - - // then - assertThat(result, equalTo(false)); - verify(sendMailSpy).initializeMail("player@example.org"); - verify(sendMailSpy, never()).sendEmail(anyString(), any(HtmlEmail.class)); - } - - @Test - public void shouldHandleFailureToSendRecoveryCode() throws EmailException { - // given - given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(7); - given(settings.getRecoveryCodeEmailMessage()).willReturn("Hi , your code is "); - SendMailSSL sendMailSpy = spy(sendMailSSL); - HtmlEmail email = mock(HtmlEmail.class); - doReturn(email).when(sendMailSpy).initializeMail(anyString()); - doReturn(false).when(sendMailSpy).sendEmail(anyString(), any(HtmlEmail.class)); - - // when - boolean result = sendMailSpy.sendRecoveryCode("John", "user@example.com", "1DEF77"); - - // then - assertThat(result, equalTo(false)); - verify(sendMailSpy).initializeMail("user@example.com"); - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSpy).sendEmail(messageCaptor.capture(), eq(email)); - assertThat(messageCaptor.getValue(), equalTo("Hi John, your code is 1DEF77")); - } - @Test public void shouldCreateEmailObject() throws EmailException { // given @@ -270,5 +133,4 @@ public class SendMailSSLTest { assertThat(mailProperties.getProperty("mail.smtp.auth.plain.disable"), equalTo("true")); assertThat(mailProperties.getProperty(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP), equalTo("oAuth2 token")); } - } diff --git a/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java index aecc158a..9ca9c7aa 100644 --- a/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java +++ b/src/test/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProviderTest.java @@ -4,7 +4,7 @@ import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.SendMailSSL; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; @@ -49,7 +49,7 @@ public class EmailRegisterExecutorProviderTest { @Mock private CommonService commonService; @Mock - private SendMailSSL sendMailSsl; + private EmailService emailService; @Mock private SyncProcessManager syncProcessManager; @Mock @@ -135,7 +135,7 @@ public class EmailRegisterExecutorProviderTest { @SuppressWarnings("unchecked") public void shouldPerformActionAfterDataSourceSave() { // given - given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); + given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); Player player = mock(Player.class); given(player.getName()).willReturn("Laleh"); RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); @@ -146,7 +146,7 @@ public class EmailRegisterExecutorProviderTest { executor.executePostPersistAction(); // then - verify(sendMailSsl).sendPasswordMail("Laleh", "test@example.com", password); + verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); verify(syncProcessManager).processSyncEmailRegister(player); } @@ -154,7 +154,7 @@ public class EmailRegisterExecutorProviderTest { @SuppressWarnings("unchecked") public void shouldHandleEmailSendingFailure() { // given - given(sendMailSsl.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(false); + given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(false); Player player = mock(Player.class); given(player.getName()).willReturn("Laleh"); RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); @@ -165,7 +165,7 @@ public class EmailRegisterExecutorProviderTest { executor.executePostPersistAction(); // then - verify(sendMailSsl).sendPasswordMail("Laleh", "test@example.com", password); + verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); verify(commonService).send(player, MessageKey.EMAIL_SEND_FAILURE); verifyZeroInteractions(syncProcessManager); } From c197a330f3d66ca2c1634c047e7c9f2b116e082c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Feb 2017 22:41:49 +0100 Subject: [PATCH 42/79] #1073 Add delay to email recovery command - Add configurable cooldown period after sending an email for /email recovery - Change ExpiringMap to remove expired entries (like ExpiringSet) - Create method to translate durations via the messages file --- .../executable/email/RecoverEmailCommand.java | 49 ++++++++++- .../fr/xephi/authme/message/MessageKey.java | 30 ++++++- .../fr/xephi/authme/message/Messages.java | 34 ++++++++ .../settings/properties/SecuritySettings.java | 7 ++ src/main/java/fr/xephi/authme/util/Utils.java | 33 -------- .../authme/util/expiring/ExpiringMap.java | 10 ++- .../authme/util/expiring/ExpiringSet.java | 11 ++- .../authme/util/expiring/TimedCounter.java | 4 +- .../email/RecoverEmailCommandTest.java | 83 +++++++++++++++---- .../message/MessagesIntegrationTest.java | 24 ++++++ .../authme/util/expiring/ExpiringSetTest.java | 39 +++++++-- 11 files changed, 252 insertions(+), 72 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index eb188f79..b3fb3b62 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -5,24 +5,31 @@ import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.RecoveryCodeService; +import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.RandomStringUtils; +import fr.xephi.authme.util.expiring.Duration; +import fr.xephi.authme.util.expiring.ExpiringSet; import org.bukkit.entity.Player; +import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.List; +import java.util.concurrent.TimeUnit; import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; /** * Command for password recovery by email. */ -public class RecoverEmailCommand extends PlayerCommand { +public class RecoverEmailCommand extends PlayerCommand implements Reloadable { @Inject private PasswordSecurity passwordSecurity; @@ -42,8 +49,19 @@ public class RecoverEmailCommand extends PlayerCommand { @Inject private RecoveryCodeService recoveryCodeService; + @Inject + private Messages messages; + + private ExpiringSet emailCooldown; + + @PostConstruct + private void initEmailCooldownSet() { + emailCooldown = new ExpiringSet<>( + commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); + } + @Override - public void runCommand(Player player, List arguments) { + protected void runCommand(Player player, List arguments) { final String playerMail = arguments.get(0); final String playerName = player.getName(); @@ -78,15 +96,29 @@ public class RecoverEmailCommand extends PlayerCommand { processRecoveryCode(player, arguments.get(1), email); } } else { - generateAndSendNewPassword(player, email); + boolean maySendMail = checkEmailCooldown(player); + if (maySendMail) { + generateAndSendNewPassword(player, email); + } } } + @Override + public void reload() { + emailCooldown.setExpiration( + commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); + } + private void createAndSendRecoveryCode(Player player, String email) { + if (!checkEmailCooldown(player)) { + return; + } + String recoveryCode = recoveryCodeService.generateCode(player.getName()); boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode); if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_CODE_SENT); + emailCooldown.add(player.getName().toLowerCase()); } else { commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); } @@ -111,8 +143,19 @@ public class RecoverEmailCommand extends PlayerCommand { boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass); if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + emailCooldown.add(player.getName().toLowerCase()); } else { commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); } } + + private boolean checkEmailCooldown(Player player) { + Duration waitDuration = emailCooldown.getExpiration(player.getName().toLowerCase()); + if (waitDuration.getDuration() > 0) { + String durationText = messages.formatDuration(waitDuration); + messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR, durationText); + return false; + } + return true; + } } diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 7546a89c..e8c3939a 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -225,7 +225,35 @@ public enum MessageKey { RECOVERY_CODE_SENT("recovery_code_sent"), /** The recovery code is not correct! Use "/email recovery [email]" to generate a new one */ - INCORRECT_RECOVERY_CODE("recovery_code_incorrect"); + INCORRECT_RECOVERY_CODE("recovery_code_incorrect"), + + /** An email was already sent recently. You must wait %time before you can send a new one. */ + EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"), + + /** second */ + SECOND("second"), + + /** seconds */ + SECONDS("seconds"), + + /** minute */ + MINUTE("minute"), + + /** minutes */ + MINUTES("minutes"), + + /** hour */ + HOUR("hour"), + + /** hours */ + HOURS("hours"), + + /** day */ + DAY("day"), + + /** days */ + DAYS("days"); + private String key; private String[] tags; diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java index 5d777ab8..d7fdcec8 100644 --- a/src/main/java/fr/xephi/authme/message/Messages.java +++ b/src/main/java/fr/xephi/authme/message/Messages.java @@ -1,11 +1,15 @@ package fr.xephi.authme.message; +import com.google.common.collect.ImmutableMap; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.util.expiring.Duration; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import javax.inject.Inject; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Class for retrieving and sending translatable messages to players. @@ -15,6 +19,20 @@ public class Messages implements Reloadable { // Custom Authme tag replaced to new line private static final String NEWLINE_TAG = "%nl%"; + /** Contains the keys of the singular messages for time units. */ + private static final Map TIME_UNIT_SINGULARS = ImmutableMap.builder() + .put(TimeUnit.SECONDS, MessageKey.SECOND) + .put(TimeUnit.MINUTES, MessageKey.MINUTE) + .put(TimeUnit.HOURS, MessageKey.HOUR) + .put(TimeUnit.DAYS, MessageKey.DAY).build(); + + /** Contains the keys of the plural messages for time units. */ + private static final Map TIME_UNIT_PLURALS = ImmutableMap.builder() + .put(TimeUnit.SECONDS, MessageKey.SECONDS) + .put(TimeUnit.MINUTES, MessageKey.MINUTES) + .put(TimeUnit.HOURS, MessageKey.HOURS) + .put(TimeUnit.DAYS, MessageKey.DAYS).build(); + private final MessageFileHandlerProvider messageFileHandlerProvider; private MessageFileHandler messageFileHandler; @@ -71,6 +89,22 @@ public class Messages implements Reloadable { return message.split("\n"); } + /** + * Returns the textual representation for the given duration. + * Note that this class only supports the time units days, hour, minutes and seconds. + * + * @param duration the duration to build a text of + * @return text of the duration + */ + public String formatDuration(Duration duration) { + long value = duration.getDuration(); + MessageKey timeUnitKey = value == 1 + ? TIME_UNIT_SINGULARS.get(duration.getTimeUnit()) + : TIME_UNIT_PLURALS.get(duration.getTimeUnit()); + + return value + " " + retrieveMessage(timeUnitKey); + } + /** * Retrieve the message from the text file. * diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index daed1a8c..496fb3b2 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -114,6 +114,13 @@ public class SecuritySettings implements SettingsHolder { public static final Property RECOVERY_CODE_HOURS_VALID = newProperty("Security.recoveryCode.validForHours", 4); + @Comment({ + "Seconds a user has to wait for before a password recovery mail may be sent again", + "This prevents an attacker from abusing AuthMe's email feature." + }) + public static final Property EMAIL_RECOVERY_COOLDOWN_SECONDS = + newProperty("Security.emailRecovery.cooldown", 60); + private SecuritySettings() { } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index e7ec1657..b7628147 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -3,7 +3,6 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; import java.util.Collection; -import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** @@ -71,36 +70,4 @@ public final class Utils { return Runtime.getRuntime().availableProcessors(); } - public static Duration convertMillisToSuitableUnit(long duration) { - TimeUnit targetUnit; - if (duration > 1000L * 60L * 60L * 24L) { - targetUnit = TimeUnit.DAYS; - } else if (duration > 1000L * 60L * 60L) { - targetUnit = TimeUnit.HOURS; - } else if (duration > 1000L * 60L) { - targetUnit = TimeUnit.MINUTES; - } else if (duration > 1000L) { - targetUnit = TimeUnit.SECONDS; - } else { - targetUnit = TimeUnit.MILLISECONDS; - } - - return new Duration(targetUnit, duration); - } - - public static final class Duration { - - private final long duration; - private final TimeUnit unit; - - Duration(TimeUnit targetUnit, long durationMillis) { - this(targetUnit, durationMillis, TimeUnit.MILLISECONDS); - } - - Duration(TimeUnit targetUnit, long sourceDuration, TimeUnit sourceUnit) { - this.duration = targetUnit.convert(sourceDuration, sourceUnit); - this.unit = targetUnit; - } - } - } diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java index 894b6486..e920805c 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java +++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java @@ -46,7 +46,13 @@ public class ExpiringMap { */ public V get(K key) { ExpiringEntry value = entries.get(key); - return value == null ? null : value.getValue(); + if (value == null) { + return null; + } else if (System.currentTimeMillis() > value.getExpiration()) { + entries.remove(key); + return null; + } + return value.getValue(); } /** @@ -115,7 +121,7 @@ public class ExpiringMap { } V getValue() { - return System.currentTimeMillis() > expiration ? null : value; + return value; } long getExpiration() { diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java index 7e711673..fea8fb31 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java +++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java @@ -83,23 +83,22 @@ public class ExpiringSet { /** * Returns the duration of the entry until it expires (provided it is not removed or re-added). - * If the entry does not exist, -1 is returned. + * If the entry does not exist, a duration of -1 seconds is returned. * * @param entry the entry whose duration before it expires should be returned - * @param unit the unit in which to return the duration * @return duration the entry will remain in the set (if there are not modifications) */ - public long getExpiration(E entry, TimeUnit unit) { + public Duration getExpiration(E entry) { Long expiration = entries.get(entry); if (expiration == null) { - return -1; + return new Duration(-1, TimeUnit.SECONDS); } long stillPresentMillis = expiration - System.currentTimeMillis(); if (stillPresentMillis < 0) { entries.remove(entry); - return -1; + return new Duration(-1, TimeUnit.SECONDS); } - return unit.convert(stillPresentMillis, TimeUnit.MILLISECONDS); + return Duration.createWithSuitableUnit(stillPresentMillis, TimeUnit.MILLISECONDS); } /** diff --git a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java index 67839294..c3ae908c 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java +++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java @@ -1,6 +1,5 @@ package fr.xephi.authme.util.expiring; -import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -42,9 +41,10 @@ public class TimedCounter extends ExpiringMap { * @return the total of all valid entries */ public int total() { + long currentTime = System.currentTimeMillis(); return entries.values().stream() + .filter(entry -> currentTime <= entry.getExpiration()) .map(ExpiringEntry::getValue) - .filter(Objects::nonNull) .reduce(0, Integer::sum); } } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java index 556a1033..5b102078 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -1,29 +1,39 @@ package fr.xephi.authme.command.executable.email; +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.message.Messages; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.RecoveryCodeService; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.expiring.Duration; import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.Mockito; import java.util.Arrays; import java.util.Collections; +import java.util.concurrent.TimeUnit; import static fr.xephi.authme.AuthMeMatchers.stringWithLength; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -38,19 +48,19 @@ import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link RecoverEmailCommand}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(DelayedInjectionRunner.class) public class RecoverEmailCommandTest { private static final String DEFAULT_EMAIL = "your@email.com"; - @InjectMocks + @InjectDelayed private RecoverEmailCommand command; @Mock private PasswordSecurity passwordSecurity; @Mock - private CommonService commandService; + private CommonService commonService; @Mock private DataSource dataSource; @@ -64,11 +74,19 @@ public class RecoverEmailCommandTest { @Mock private RecoveryCodeService recoveryCodeService; + @Mock + private Messages messages; + @BeforeClass public static void initLogger() { TestHelper.setupLogger(); } + @BeforeInjecting + public void initSettings() { + given(commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS)).willReturn(40); + } + @Test public void shouldHandleMissingMailProperties() { // given @@ -79,7 +97,7 @@ public class RecoverEmailCommandTest { command.executeCommand(sender, Collections.singletonList("some@email.tld")); // then - verify(commandService).send(sender, MessageKey.INCOMPLETE_EMAIL_SETTINGS); + verify(commonService).send(sender, MessageKey.INCOMPLETE_EMAIL_SETTINGS); verifyZeroInteractions(dataSource, passwordSecurity); } @@ -98,7 +116,7 @@ public class RecoverEmailCommandTest { // then verify(emailService).hasAllInformation(); verifyZeroInteractions(dataSource); - verify(commandService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR); + verify(commonService).send(sender, MessageKey.ALREADY_LOGGED_IN_ERROR); } @Test @@ -118,7 +136,7 @@ public class RecoverEmailCommandTest { verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); - verify(commandService).send(sender, MessageKey.USAGE_REGISTER); + verify(commonService).send(sender, MessageKey.USAGE_REGISTER); } @Test @@ -138,7 +156,7 @@ public class RecoverEmailCommandTest { verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); - verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + verify(commonService).send(sender, MessageKey.INVALID_EMAIL); } @Test @@ -158,7 +176,7 @@ public class RecoverEmailCommandTest { verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verifyNoMoreInteractions(dataSource); - verify(commandService).send(sender, MessageKey.INVALID_EMAIL); + verify(commonService).send(sender, MessageKey.INVALID_EMAIL); } @Test @@ -183,7 +201,7 @@ public class RecoverEmailCommandTest { verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); verify(recoveryCodeService).generateCode(name); - verify(commandService).send(sender, MessageKey.RECOVERY_CODE_SENT); + verify(commonService).send(sender, MessageKey.RECOVERY_CODE_SENT); verify(emailService).sendRecoveryCode(name, email, code); } @@ -207,7 +225,7 @@ public class RecoverEmailCommandTest { // then verify(emailService).hasAllInformation(); verify(dataSource, only()).getAuth(name); - verify(commandService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE); + verify(commonService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE); verifyNoMoreInteractions(emailService); } @@ -224,7 +242,7 @@ public class RecoverEmailCommandTest { String code = "A6EF3AC8"; PlayerAuth auth = newAuthWithEmail(email); given(dataSource.getAuth(name)).willReturn(auth); - given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20); + given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20); given(passwordSecurity.computeHash(anyString(), eq(name))) .willAnswer(invocation -> new HashedPassword(invocation.getArgument(0))); given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(true); @@ -243,7 +261,7 @@ public class RecoverEmailCommandTest { verify(dataSource).updatePassword(eq(name), any(HashedPassword.class)); verify(recoveryCodeService).removeCode(name); verify(emailService).sendPasswordMail(name, email, generatedPassword); - verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } @Test @@ -258,7 +276,7 @@ public class RecoverEmailCommandTest { String email = "shark@example.org"; PlayerAuth auth = newAuthWithEmail(email); given(dataSource.getAuth(name)).willReturn(auth); - given(commandService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20); + given(commonService.getProperty(EmailSettings.RECOVERY_PASSWORD_LENGTH)).willReturn(20); given(passwordSecurity.computeHash(anyString(), eq(name))) .willAnswer(invocation -> new HashedPassword(invocation.getArgument(0))); given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(false); @@ -275,7 +293,40 @@ public class RecoverEmailCommandTest { assertThat(generatedPassword, stringWithLength(20)); verify(dataSource).updatePassword(eq(name), any(HashedPassword.class)); verify(emailService).sendPasswordMail(name, email, generatedPassword); - verify(commandService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + } + + @Test + public void shouldNotSendEmailIfCooldownCheckFails() { + // given + String name = "feverRay"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(emailService.hasAllInformation()).willReturn(true); + given(emailService.sendRecoveryCode(anyString(), anyString(), anyString())).willReturn(true); + given(playerCache.isAuthenticated(name)).willReturn(false); + String email = "mymail@example.org"; + PlayerAuth auth = newAuthWithEmail(email); + given(dataSource.getAuth(name)).willReturn(auth); + given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(true); + given(recoveryCodeService.generateCode(anyString())).willReturn("Code"); + // Trigger sending of recovery code + command.executeCommand(sender, Collections.singletonList(email)); + + Mockito.reset(emailService, commonService); + given(emailService.hasAllInformation()).willReturn(true); + given(messages.formatDuration(any(Duration.class))).willReturn("8 minutes"); + + // when + command.executeCommand(sender, Collections.singletonList(email)); + + // then + verify(emailService, only()).hasAllInformation(); + ArgumentCaptor durationCaptor = ArgumentCaptor.forClass(Duration.class); + verify(messages).formatDuration(durationCaptor.capture()); + assertThat(durationCaptor.getValue().getDuration(), both(lessThan(41L)).and(greaterThan(36L))); + assertThat(durationCaptor.getValue().getTimeUnit(), equalTo(TimeUnit.SECONDS)); + verify(messages).send(sender, MessageKey.EMAIL_COOLDOWN_ERROR, "8 minutes"); } diff --git a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java index 94fe9a0d..8f48fffe 100644 --- a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java @@ -1,7 +1,9 @@ package fr.xephi.authme.message; +import com.google.common.collect.ImmutableMap; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.util.expiring.Duration; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; @@ -11,6 +13,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.io.File; +import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.logging.Logger; @@ -230,6 +234,26 @@ public class MessagesIntegrationTest { assertThat(result, equalTo("Use /captcha 24680 to solve the captcha")); } + @Test + public void shouldFormatDurationObjects() { + // given + Map expectedTexts = ImmutableMap.builder() + .put(new Duration(1, TimeUnit.SECONDS), "1 second") + .put(new Duration(12, TimeUnit.SECONDS), "12 seconds") + .put(new Duration(1, TimeUnit.MINUTES), "1 minute") + .put(new Duration(0, TimeUnit.MINUTES), "0 minutes") + .put(new Duration(1, TimeUnit.HOURS), "1 hour") + .put(new Duration(-4, TimeUnit.HOURS), "-4 hours") + .put(new Duration(1, TimeUnit.DAYS), "1 day") + .put(new Duration(44, TimeUnit.DAYS), "44 days") + .build(); + + // when / then + for (Map.Entry entry : expectedTexts.entrySet()) { + assertThat(messages.formatDuration(entry.getKey()), equalTo(entry.getValue())); + } + } + @SuppressWarnings("unchecked") private static MessageFileHandlerProvider providerReturning(File file, String defaultFile) { MessageFileHandlerProvider handler = mock(MessageFileHandlerProvider.class); diff --git a/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java index 15a55aa0..42180ab2 100644 --- a/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java +++ b/src/test/java/fr/xephi/authme/util/expiring/ExpiringSetTest.java @@ -4,7 +4,6 @@ import org.junit.Test; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; @@ -97,14 +96,31 @@ public class ExpiringSetTest { set.add("my entry"); // when - long expiresInHours = set.getExpiration("my entry", TimeUnit.HOURS); - long expiresInMinutes = set.getExpiration("my entry", TimeUnit.MINUTES); - long unknownExpires = set.getExpiration("bogus", TimeUnit.SECONDS); + Duration expiration = set.getExpiration("my entry"); + Duration unknownExpiration = set.getExpiration("bogus"); // then - assertThat(expiresInHours, equalTo(2L)); - assertThat(expiresInMinutes, either(equalTo(122L)).or(equalTo(123L))); - assertThat(unknownExpires, equalTo(-1L)); + assertIsDuration(expiration, 2, TimeUnit.HOURS); + assertIsDuration(unknownExpiration, -1, TimeUnit.SECONDS); + } + + @Test + public void shouldReturnExpirationInSuitableUnits() { + // given + ExpiringSet set = new ExpiringSet<>(601, TimeUnit.SECONDS); + set.add(12); + set.setExpiration(49, TimeUnit.HOURS); + set.add(23); + + // when + Duration expiration12 = set.getExpiration(12); + Duration expiration23 = set.getExpiration(23); + Duration expectedUnknown = set.getExpiration(-100); + + // then + assertIsDuration(expiration12, 10, TimeUnit.MINUTES); + assertIsDuration(expiration23, 2, TimeUnit.DAYS); + assertIsDuration(expectedUnknown, -1, TimeUnit.SECONDS); } @Test @@ -114,9 +130,14 @@ public class ExpiringSetTest { set.add(23); // when - long expiresInSeconds = set.getExpiration(23, TimeUnit.SECONDS); + Duration expiration = set.getExpiration(23); // then - assertThat(expiresInSeconds, equalTo(-1L)); + assertIsDuration(expiration, -1, TimeUnit.SECONDS); + } + + private static void assertIsDuration(Duration duration, long expectedDuration, TimeUnit expectedUnit) { + assertThat(duration.getTimeUnit(), equalTo(expectedUnit)); + assertThat(duration.getDuration(), equalTo(expectedDuration)); } } From 33c4a4690fe7063199fd197f9c3fd49d11238a9f Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Feb 2017 22:42:23 +0100 Subject: [PATCH 43/79] #1073 Email recovery delay: update project files following new messages & config --- docs/config.md | 10 +++- docs/translations.md | 59 +++++++++---------- src/main/resources/messages/messages_bg.yml | 13 +++- src/main/resources/messages/messages_br.yml | 11 ++++ src/main/resources/messages/messages_cz.yml | 11 ++++ src/main/resources/messages/messages_de.yml | 11 ++++ src/main/resources/messages/messages_en.yml | 11 ++++ src/main/resources/messages/messages_es.yml | 11 ++++ src/main/resources/messages/messages_eu.yml | 15 ++++- src/main/resources/messages/messages_fi.yml | 13 +++- src/main/resources/messages/messages_fr.yml | 11 ++++ src/main/resources/messages/messages_gl.yml | 13 +++- src/main/resources/messages/messages_hu.yml | 11 ++++ src/main/resources/messages/messages_id.yml | 13 +++- src/main/resources/messages/messages_it.yml | 11 ++++ src/main/resources/messages/messages_ko.yml | 13 +++- src/main/resources/messages/messages_lt.yml | 13 +++- src/main/resources/messages/messages_nl.yml | 11 ++++ src/main/resources/messages/messages_pl.yml | 11 ++++ src/main/resources/messages/messages_pt.yml | 13 +++- src/main/resources/messages/messages_ro.yml | 11 ++++ src/main/resources/messages/messages_ru.yml | 11 ++++ src/main/resources/messages/messages_sk.yml | 15 ++++- src/main/resources/messages/messages_tr.yml | 13 +++- src/main/resources/messages/messages_uk.yml | 13 +++- src/main/resources/messages/messages_vn.yml | 11 ++++ src/main/resources/messages/messages_zhcn.yml | 11 ++++ src/main/resources/messages/messages_zhhk.yml | 13 +++- src/main/resources/messages/messages_zhmc.yml | 11 ++++ src/main/resources/messages/messages_zhtw.yml | 13 +++- 30 files changed, 360 insertions(+), 47 deletions(-) diff --git a/docs/config.md b/docs/config.md index 4b3396b5..ff2d54fb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ - + ## AuthMe Configuration The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder, @@ -422,6 +422,8 @@ Security: maxLoginTry: 5 # Captcha length captchaLength: 5 + # Minutes after which login attempts count is reset for a player + captchaCountReset: 60 tempban: # Tempban a user's IP address if they enter the wrong password too many times enableTempban: false @@ -438,6 +440,10 @@ Security: length: 8 # How many hours is a recovery code valid for? validForHours: 4 + emailRecovery: + # Seconds a user has to wait for before a password recovery mail may be sent again + # This prevents an attacker from abusing AuthMe's email feature. + cooldown: 60 BackupSystem: # Enable or disable automatic backup ActivateBackup: false @@ -454,4 +460,4 @@ To change settings on a running server, save your changes to config.yml and use --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Feb 05 13:46:19 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Feb 25 21:59:18 CET 2017 diff --git a/docs/translations.md b/docs/translations.md index dee35ed7..a4bc104b 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -8,35 +8,34 @@ in your config.yml to use the language, or use another language code to start a Code | Language | Translated |   ---- | -------- | ---------: | ------ [en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | bar -[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 69% | bar -[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% | bar -[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% | bar -[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 100% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar -[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 62% | bar -[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 66% | bar -[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | bar -[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 70% | bar -[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 99% | bar -[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 70% | bar -[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | bar -[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 72% | bar -[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 53% | bar -[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 77% | bar -[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar -[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 86% | bar -[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 99% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 99% | bar -[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 46% | bar -[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 81% | bar -[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 93% | bar -[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 100% | bar -[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 81% | bar -[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 81% | bar -[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 96% | bar -[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 81% | bar - +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 61% | bar +[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% | bar +[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% | bar +[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 89% | bar +[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% | bar +[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% | bar +[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% | bar +[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% | bar +[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 89% | bar +[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | bar +[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | bar +[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | bar +[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 89% | bar +[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 77% | bar +[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% | bar +[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | bar +[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 72% | bar +[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% | bar +[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 89% | bar +[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% | bar +[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 72% | bar +[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | bar +[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | bar --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Jan 11 21:24:50 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Feb 25 21:59:17 CET 2017 diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 977e8eaf..38e79fac 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -45,7 +45,7 @@ unregistered: '&cУспешно от-регистриран!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' usage_unreg: '&cКоманда: /unregister парола' pwd_changed: '&cПаролата е променена!' @@ -87,8 +87,19 @@ email_send: '[AuthMe] Изпраен е имейл !' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cМоля добави своя имейл с : /email add имейл имейл' recovery_email: '&cЗабравихте своята парола? Моля използвай /email recovery <имейл>' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cYou need to type a captcha, please type: /captcha ' wrong_captcha: '&cГрешен код, използвай : /captcha THE_CAPTCHA' valid_captcha: '&cТвоя код е валиден!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index a6117b3b..c213f49c 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -90,8 +90,19 @@ email_send_failure: '&cO e-mail não pôde ser enviado, reporte isso a um admini show_no_email: '&2Você atualmente não têm endereço de e-mail associado a esta conta.' add_email: '&3Por favor, adicione seu e-mail para a sua conta com o comando "/email add "' recovery_email: '&3Esqueceu sua senha? Por favor, use o comando "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Para iniciar sessão você tem que resolver um código captcha, utilize o comando "/captcha "' wrong_captcha: '&cCaptcha errado, por favor, escreva "/captcha THE_CAPTCHA" no chat!' valid_captcha: '&2Código Captcha resolvido corretamente!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 8b653fb4..586a7376 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -86,8 +86,19 @@ email_send_failure: 'Email nemohl být odeslán. Kontaktujte prosím admina.' show_no_email: '&2K tomuto účtu nemáte přidanou žádnou emailovou adresu.' add_email: '&cPřidej prosím svůj email pomocí : /email add TvůjEmail TvůjEmail' recovery_email: '&cZapomněl jsi heslo? Napiš: /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cPoužij: /captcha ' wrong_captcha: '&cŠpatné opsana Captcha, pouzij prosim: /captcha THE_CAPTCHA' valid_captcha: '&cZadaná captcha je v pořádku!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 474c8237..648a5153 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -86,8 +86,19 @@ email_send_failure: 'Die E-Mail konnte nicht gesendet werden. Bitte kontaktiere show_no_email: '&2Du hast zur Zeit keine E-Mail-Adresse für deinen Account hinterlegt.' add_email: '&3Bitte hinterlege deine E-Mail-Adresse: /email add ' recovery_email: '&3Passwort vergessen? Nutze "/email recovery " für ein neues Passwort' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Um dich einzuloggen, tippe dieses Captcha so ein: /captcha ' wrong_captcha: '&cFalsches Captcha, bitte nutze: /captcha THE_CAPTCHA' valid_captcha: '&2Das Captcha ist korrekt!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index d1b14700..c7190234 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -86,8 +86,19 @@ email_send_failure: 'The email could not be sent. Please contact an administrato show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&3Please add your email to your account with the command: /email add ' recovery_email: '&3Forgot your password? Please use the command: /email recovery ' +email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha ' wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' valid_captcha: '&2Captcha code solved correctly!' + +# Time units +second: 'second' +seconds: 'seconds' +minute: 'minute' +minutes: 'minutes' +hour: 'hour' +hours: 'hours' +day: 'day' +days: 'days' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index e0c0c96d..ac741571 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -89,8 +89,19 @@ email_send_failure: 'No se ha podido enviar el correo electrónico. Por favor, c show_no_email: '&2No tienes ningun E-Mail asociado en esta cuenta.' add_email: '&cPor favor agrega tu e-mail con: /email add tuEmail confirmarEmail' recovery_email: '&c¿Olvidaste tu contraseña? Por favor usa /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cUso: /captcha ' wrong_captcha: '&cCaptcha incorrecto, por favor usa: /captcha THE_CAPTCHA' valid_captcha: '&c¡ Captcha ingresado correctamente !' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 53b0704d..05473e68 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -45,7 +45,7 @@ unregistered: '&cZure erregistroa ezabatu duzu!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fZure kontua aktibatu gabe dago, konfirmatu zure emaila!' usage_unreg: '&cErabili: /unregister password' pwd_changed: '&cPasahitza aldatu duzu!' @@ -87,8 +87,19 @@ email_send: '[AuthMe] Berreskuratze emaila bidalita !' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cMesedez gehitu zure emaila : /email add yourEmail confirmEmail' recovery_email: '&cPasahitza ahaztu duzu? Erabili /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha -# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' +# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha ' # TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' # TODO valid_captcha: '&2Captcha code solved correctly!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index bf359e8c..a48ac74d 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -45,7 +45,7 @@ unregistered: '&cPelaajatili poistettu onnistuneesti!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fKäyttäjäsi ei ole vahvistettu!' usage_unreg: '&cKäyttötapa: /unregister password' pwd_changed: '&cSalasana vaihdettu!!' @@ -87,8 +87,19 @@ email_send: '[AuthMe] Palautus sähköposti lähetetty!' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cLisää sähköpostisi: /email add sähköpostisi sähköpostisiUudelleen' recovery_email: '&cUnohtuiko salasana? Käytä komentoa: /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cKäyttötapa: /captcha ' wrong_captcha: '&cVäärä varmistus, käytä : /captcha THE_CAPTCHA' valid_captcha: '&cSinun varmistus onnistui.!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 1bd88456..dd01ccc6 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -91,8 +91,19 @@ email_send_failure: '&cL''email n''a pas pu être envoyé. Veuillez contacter un show_no_email: 'Vous n''avez aucune adresse email enregistré sur votre compte.' add_email: '&cMerci d''ajouter votre email : /email add ' recovery_email: '&cVous avez oublié votre Mot de Passe? Utilisez /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cTrop de tentatives de connexion échouées, utilisez: /captcha ' wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau : /captcha THE_CAPTCHA' valid_captcha: '&aCaptché validé! Veuillez maintenant vous connecter.' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index 2739a1a8..1c057030 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -45,7 +45,7 @@ unregistered: '&cFeito! Xa non estás rexistrado!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fA túa conta aínda non está activada, comproba a túa bandexa de correo!!' usage_unreg: '&cUso: /unregister ' pwd_changed: '&cCambiouse o contrasinal!' @@ -87,8 +87,19 @@ email_send: '[AuthMe] Enviouse o correo de confirmación!' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cPor favor, engade o teu correo electrónico con: /email add ' recovery_email: '&cOlvidaches o contrasinal? Por favor, usa /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cNecesitas escribir un captcha, por favor escribe: /captcha ' wrong_captcha: '&cCaptcha equivocado, por favor usa: /captcha THE_CAPTCHA' valid_captcha: '&cO teu captcha é válido !' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index f5667eb8..dc62f8c1 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -86,8 +86,19 @@ email_already_used: '&4Ez az email cím már használatban van!' show_no_email: '&2Ehhez a felhasználóhoz jelenleg még nincs email hozzárendelve.' add_email: '&3Kérlek rendeld hozzá a felhasználódhoz az email címedet "/email add "' recovery_email: '&3Ha elfelejtetted a jelszavad, használd az: "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3A bejelentkezéshez CAPTCHA szükséges, kérlek használd a következő parancsot "/captcha "' wrong_captcha: '&cHibás CAPTCHA, kérlek írd be a következő parancsot: "/captcha THE_CAPTCHA"!' valid_captcha: '&2CAPTCHA sikeresen feloldva!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index b4fde4ab..90db87bd 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -45,7 +45,7 @@ unregistered: '&cUnregister berhasil!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&cAkunmu belum diaktifkan, silahkan periksa email kamu!' # TODO usage_unreg: '&cUsage: /unregister ' pwd_changed: '&2Berhasil mengubah password!' @@ -87,8 +87,19 @@ email_exists: '&cEmail pemulihan sudah dikirim! kamu bisa membatalkan dan mengir # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&3Silahkan tambahkan email ke akunmu menggunakan command "/email add "' recovery_email: '&3Lupa password? silahkan gunakan command "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Kamu harus menyelesaikan kode captcha untuk login, silahkan gunakan command "/captcha "' wrong_captcha: '&cCaptcha salah, gunakan command "/captcha THE_CAPTCHA" pada chat!' valid_captcha: '&2Kode captcha terselesaikan!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index cdf6fed8..ede27690 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -88,8 +88,19 @@ email_send_failure: 'Non è stato possibile inviare l''email contenente la tua n show_no_email: '&2Al momento non hai nessun indirizzo email associato al tuo account.' add_email: '&3Per poter recuperare la password in futuro, aggiungi un indirizzo email al tuo account con il comando: /email add ' recovery_email: '&3Hai dimenticato la tua password? Puoi recuperarla eseguendo il comando: /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Per poterti autenticare devi risolvere un captcha, per favore scrivi: /captcha ' wrong_captcha: '&cCaptcha sbagliato, per favore riprova scrivendo: "/captcha THE_CAPTCHA" in chat!' valid_captcha: '&2Il captcha inserito è valido!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index db7bc8aa..a65d9352 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -49,7 +49,7 @@ unregistered: '&c성공적으로 탈퇴했습니다!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!' usage_unreg: '&c사용법: /unregister 비밀번호' pwd_changed: '&c비밀번호를 변경했습니다!' @@ -91,8 +91,19 @@ email_exists: '[AuthMe] 당신의 계정에 이미 이메일이 존재합니다. # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&c당신의 이메일을 추가해주세요 : /email add 당신의이메일 이메일재입력' recovery_email: '&c비밀번호를 잊어버리셨다고요? /email recovery <당신의이메일>을 사용하세요' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&c보안문자 입력이 필요합니다, 입력해주세요: /captcha ' wrong_captcha: '&c잘못된 보안문자, 사용해주세요 : /captcha THE_CAPTCHA' valid_captcha: '&c당신의 보안문자는 적합합니다!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index a2bcbe25..d75dec4a 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -45,7 +45,7 @@ unregistered: '&aSekmingai issiregistravote!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&aJusu vartotojas nera patvirtintas, patikrinkite el.pasta.' usage_unreg: '&ePanaikinti registracija: "/unregister slaptazodis"' pwd_changed: '&aSlaptazodis pakeistas' @@ -87,8 +87,19 @@ same_nick: '&cKazkas situo vardu jau zaidzia.' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&ePrasau pridekite savo el.pasta : /email add Email confirmEmail' recovery_email: '&cPamirsote slaptazodi? Rasykite: /email recovery el.pastas' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cPanaudojimas: /captcha ' wrong_captcha: '&cNeteisinga Captcha, naudokite : /captcha THE_CAPTCHA' valid_captcha: '&cJusu captcha Teisinga!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 2ee68f95..630fadd6 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -86,8 +86,19 @@ email_send_failure: 'De E-mail kon niet verzonden worden. Neem contact op met ee show_no_email: '&2Je hebt nog geen E-mailadres toegevoegd aan dit account.' add_email: '&3Voeg jouw E-mailadres alsjeblieft toe met: /email add ' recovery_email: '&3Wachtwoord vergeten? Gebruik alsjeblieft het commando: /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Om in te loggen moet je een captcha-code oplossen, gebruik het commando: /captcha ' wrong_captcha: '&cVerkeerde captcha-code, typ alsjeblieft "/captcha THE_CAPTCHA" in de chat!' valid_captcha: '&2De captcha-code is geldig!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 3f9e37ae..549efb9e 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -87,8 +87,19 @@ email_send_failure: 'Nie mozna wyslac emaila. Skontaktuj sie z administracja.' show_no_email: '&2Nie posiadasz adresu email przypisanego do tego konta.' add_email: '&cProsze dodac swoj email: /email add twojEmail powtorzEmail' recovery_email: '&cZapomniales hasla? Prosze uzyj komendy /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cWpisz: /captcha ' wrong_captcha: '&cZly kod, prosze wpisac: /captcha THE_CAPTCHA' valid_captcha: '&cTwoj kod jest nieprawidlowy!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 7254f8bb..fef35f74 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -45,7 +45,7 @@ unregistered: '&cRegisto eliminado com sucesso!' # TODO accounts_owned_other: 'The player %name has %count accounts:' two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' usage_unreg: '&cUse: /unregister password' pwd_changed: '&cPassword alterada!' @@ -87,8 +87,19 @@ email_already_used: '&4O endereço de e-mail já está sendo usado' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail' recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index d1726d69..703b93f5 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -86,8 +86,19 @@ email_already_used: '&4Email-ul a fost deja folosit' show_no_email: '&2Nu ai nici-o adresa de email asociat cu acest cont.' add_email: '&3Te rugam adaugati email-ul la contul tau folosind comanda "/email add "' recovery_email: '&3Ti-ai uitat parola? Te rugam foloseste comanda "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Pentru a te autentifica trebuie sa folosesti codul de la captcha, te rugam foloseste comanda "/captcha "' wrong_captcha: '&cCod-ul captcha este gresit, te rugam foloseste comanda "/captcha THE_CAPTCHA"!' valid_captcha: '&2Cod-ul captcha a fost scris corect!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 873b6397..11f55835 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -86,8 +86,19 @@ email_send_failure: 'Письмо не може быть отправлено. show_no_email: '&2В данный момент к вашему аккаунте не привязана электронная почта.' add_email: '&cДобавьте свой email: &e/email add <Ваш Email> <Ваш Email>' recovery_email: '&cЗабыли пароль? Используйте &e/email recovery <Ваш Email>' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Каптча usage_captcha: '&cВы должны ввести код, используйте: &e/captcha ' wrong_captcha: '&cНеверный код, используйте: &e/captcha THE_CAPTCHA' valid_captcha: '&2Вы успешно ввели код!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index 5eb97d92..b49f8cf5 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -49,7 +49,7 @@ unregistered: '&cUcet bol vymazany!' # TODO accounts_owned_other: 'The player %name has %count accounts:' # TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&fUcet nie je aktivny. Prezri si svoj e-mail!' usage_unreg: '&cPríkaz: /unregister heslo' pwd_changed: '&cHeslo zmenené!' @@ -91,8 +91,19 @@ same_nick: '&fHrác s tymto nickom uz hrá!' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&cPridaj svoj e-mail príkazom "/email add email zopakujEmail"' recovery_email: '&cZabudol si heslo? Pouzi príkaz /email recovery ' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha -# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha "' +# TODO usage_captcha: '&3To login you have to solve a captcha code, please use the command: /captcha ' # TODO wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!' # TODO valid_captcha: '&2Captcha code solved correctly!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 480e0e5b..a3ba1f26 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -44,7 +44,7 @@ unregistered: '&cKayit basariyla kaldirildi!' # TODO accounts_owned_other: 'The player %name has %count accounts:' two_factor_create: '&2Gizli kodunuz %code. Buradan test edebilirsin, %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!' usage_unreg: '&cKullanim: /unregister ' pwd_changed: '&2Sifre basariyla degistirildi!' @@ -86,8 +86,19 @@ email_already_used: '&4Eposta adresi zaten kullaniliyor.' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&3Lutfen hesabinize eposta adresinizi komut ile ekleyin "/email add "' recovery_email: '&3Sifreni mi unuttun ? Komut kullanarak ogrenebilirsin "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captcha "' wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" sohbete yazin!' valid_captcha: '&2Guvenlik kodu dogrulandi!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index d1d53187..35dbbffe 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -44,7 +44,7 @@ accounts_owned_self: 'Кількість ваших твінк‒акаунті accounts_owned_other: 'Кількість твінк‒акаунтів гравця %name: %count' two_factor_create: '&2Ваш секретний код — %code %nl%&2Можете зкопіювати його за цим посиланням — %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&cВаш акаунт ще не активовано. Будь ласка, провірте свою електронну пошту!' usage_unreg: '&cСинтаксис: /unregister <пароль>' pwd_changed: '&2Пароль успішно змінено!' @@ -86,8 +86,19 @@ email_already_used: '&4До цієї електронної пошти прив # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&3Не забудьте прив’язати електронну пошту до свого акаунта, за допомогою команди "/email add "' recovery_email: 'Забули пароль? Можете скористатись командою &9/email recovery &f<&9ваш e-mail&f>' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3Для продовження доведеться ввести капчу — "/captcha "' wrong_captcha: '&cНевірно введена капча! Спробуйте ще раз — "/captcha THE_CAPTCHA"' valid_captcha: '&2Капчу прийнято.' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index 4cb86ae8..7f68be06 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -86,8 +86,19 @@ email_send_failure: 'Không thể gửi thư. Vui lòng liên hệ với ban qu show_no_email: '&2Hiện tại bạn chưa liên kết bất kỳ email nào với tài khoản này.' add_email: '&eVui lòng thêm email của bạn với lệnh "/email add "' recovery_email: '&aBạn quên mật khẩu? Vui lòng gõ lệnh "/email recovery "' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&eĐể đăng nhập vui lòng hãy gõ mã Captcha, gõ lệnh "/captcha "' wrong_captcha: '&cSai mã captcha, Vui lòng nhấn "/captcha THE_CAPTCHA" trong kênh chát!' valid_captcha: '&2Mã captcha đã được xác nhận!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index e6c5e755..63922334 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -87,8 +87,19 @@ email_send_failure: '邮件发送失败,请联系管理员' show_no_email: '&2您当前并没有任何邮箱与该账号绑定' add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号' recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&8[&6玩家系统&8] &c正确用法:/captcha ' wrong_captcha: '&8[&6玩家系统&8] &c错误的验证码,请输入:“/captcha THE_CAPTCHA”' valid_captcha: '&8[&6玩家系统&8] &c你的验证码是有效的!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index a07d5249..896003d4 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -49,7 +49,7 @@ unregistered: '&8[&6用戶系統&8] &c你已成功刪除會員註冊記錄。' # TODO accounts_owned_other: 'The player %name has %count accounts:' two_factor_create: '&8[&6用戶系統 - 兩步驗證碼&8] &b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&8[&6用戶系統&8] &f你的帳戶還沒有經過電郵驗證 !' usage_unreg: '&8[&6用戶系統&8] &f用法: 《 /unregister <密碼> 》' pwd_changed: '&8[&6用戶系統&8] &c你成功更換了你的密碼 !' @@ -91,8 +91,19 @@ email_already_used: '&8[&6用戶系統&8] &4這個電郵地址已被使用。' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&8[&6用戶系統&8] &b請為你的帳戶立即添加電郵地址: 《 /email add <電郵地址> <重覆電郵地址> 》' recovery_email: '&8[&6用戶系統&8] &b忘記密碼?請使用 /email recovery <電郵地址> 來更新密碼。' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&8[&6用戶系統&8] &f用法: 《 /captcha 》' wrong_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效,請使用 《 /captcha THE_CAPTCHA 》 再次輸入。' valid_captcha: '&8[&6用戶系統&8] &c你所輸入的驗證碼無效 !' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index 1d9876ad..92f9113e 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -86,8 +86,19 @@ email_already_used: '&4此電子郵件地址已被使用' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&3請使用命令: /email add [你的電郵地址] [重覆確認你的電郵地址] 將您的電子郵件添加到您的帳戶"' recovery_email: '&3忘記密碼了嗎? 請使用命令: "/email recovery [你的電郵地址]"' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&3T要登錄您必須使用captcha驗證碼,請使用命令: "/captcha "' wrong_captcha: '&c驗證碼錯誤!請按T在聊天中輸入 "/captcha THE_CAPTCHA"' valid_captcha: '&2驗證碼正確!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index b5dc97cf..b2cff038 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -49,7 +49,7 @@ unregistered: '&b【AuthMe】&6你已經成功取消註冊。' # TODO accounts_owned_other: 'The player %name has %count accounts:' two_factor_create: '&b【AuthMe - 兩步驗證碼】&b你的登入金鑰為&9「%c%code&9」&b,掃描連結為:&c %url' # TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' +# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' vb_nonActiv: '&b【AuthMe】&6你的帳號還沒有經過驗證! 檢查看看你的電子信箱 (Email) 吧!' usage_unreg: '&b【AuthMe】&6用法: &c"/unregister <密碼>"' pwd_changed: '&b【AuthMe】&6密碼變更成功!' @@ -91,8 +91,19 @@ email_already_used: '&b【AuthMe】&4這個電郵地址已被使用。' # TODO show_no_email: '&2You currently don''t have email address associated with this account.' add_email: '&b【AuthMe】&6請使用 &c"/email add <你的Email> <再次輸入你的Email>" &6來添加 Email' recovery_email: '&b【AuthMe】&6忘記密碼了嗎? 使用 &c"/email recovery <你的Email>"' +# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha usage_captcha: '&b【AuthMe】&6請用 &c"/captcha " &6來輸入你的驗證碼' wrong_captcha: '&b【AuthMe】&6錯誤的驗證碼,請使用 《 /captcha THE_CAPTCHA 》 再試一次吧。' valid_captcha: '&b【AuthMe】&6驗證碼無效!' + +# Time units +# TODO second: 'second' +# TODO seconds: 'seconds' +# TODO minute: 'minute' +# TODO minutes: 'minutes' +# TODO hour: 'hour' +# TODO hours: 'hours' +# TODO day: 'day' +# TODO days: 'days' From a2b8ca683d2bbbb1cec9369bd5b2e69ad2fb2b5e Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Feb 2017 23:37:15 +0100 Subject: [PATCH 44/79] Add tests for lazy tag replacement --- .../fr/xephi/authme/util/PlayerUtilsTest.java | 6 ++ .../authme/util/RandomStringUtilsTest.java | 7 ++ .../authme/util/lazytags/TagBuilderTest.java | 50 +++++++++++ .../authme/util/lazytags/TagReplacerTest.java | 90 +++++++++++++++++++ .../util/lazytags/WrappedTagReplacerTest.java | 76 ++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 src/test/java/fr/xephi/authme/util/lazytags/TagBuilderTest.java create mode 100644 src/test/java/fr/xephi/authme/util/lazytags/TagReplacerTest.java create mode 100644 src/test/java/fr/xephi/authme/util/lazytags/WrappedTagReplacerTest.java diff --git a/src/test/java/fr/xephi/authme/util/PlayerUtilsTest.java b/src/test/java/fr/xephi/authme/util/PlayerUtilsTest.java index be5425c2..d16a8547 100644 --- a/src/test/java/fr/xephi/authme/util/PlayerUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/PlayerUtilsTest.java @@ -64,4 +64,10 @@ public class PlayerUtilsTest { // then assertThat(result, equalTo(name)); } + + @Test + public void shouldHaveHiddenConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(PlayerUtils.class); + } } diff --git a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java index c0f9cd0b..b5200b01 100644 --- a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.util; +import fr.xephi.authme.TestHelper; import org.junit.Test; import java.util.regex.Pattern; @@ -68,4 +69,10 @@ public class RandomStringUtilsTest { // then - throw exception } + @Test + public void shouldHaveHiddenConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(RandomStringUtils.class); + } + } diff --git a/src/test/java/fr/xephi/authme/util/lazytags/TagBuilderTest.java b/src/test/java/fr/xephi/authme/util/lazytags/TagBuilderTest.java new file mode 100644 index 00000000..60b5b68e --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/lazytags/TagBuilderTest.java @@ -0,0 +1,50 @@ +package fr.xephi.authme.util.lazytags; + +import fr.xephi.authme.TestHelper; +import org.junit.Test; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link TagBuilder}. + */ +public class TagBuilderTest { + + @Test + public void shouldCreateNoArgsTag() { + // given + Supplier supplier = () -> "hello"; + + // when + Tag tag = TagBuilder.createTag("hey", supplier); + + // then + assertThat(tag, instanceOf(SimpleTag.class)); + assertThat(tag.getValue(null), equalTo("hello")); + } + + @Test + public void shouldCreateDependentTag() { + // given + Function function = d -> Double.toString(d + d/10); + + // when + Tag tag = TagBuilder.createTag("%test", function); + + // then + assertThat(tag, instanceOf(DependentTag.class)); + assertThat(tag.getValue(24d), equalTo("26.4")); + } + + @Test + public void shouldHaveHiddenConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(TagBuilder.class); + } + +} diff --git a/src/test/java/fr/xephi/authme/util/lazytags/TagReplacerTest.java b/src/test/java/fr/xephi/authme/util/lazytags/TagReplacerTest.java new file mode 100644 index 00000000..8c9a8187 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/lazytags/TagReplacerTest.java @@ -0,0 +1,90 @@ +package fr.xephi.authme.util.lazytags; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static fr.xephi.authme.util.lazytags.TagBuilder.createTag; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link TagReplacer}. + */ +public class TagReplacerTest { + + @Test + public void shouldReplaceTags() { + // given + TestTagService tagService = new TestTagService(); + List> tags = tagService.getAvailableTags(); + List messages = Arrays.asList("pi = %PI", "for i = %self, i^2 = %square", "%self %self %PI"); + + // when + TagReplacer tagReplacer = TagReplacer.newReplacer(tags, messages); + List result = tagReplacer.getAdaptedMessages(3); + + // then + assertThat(tagService.piCount, equalTo(1)); + assertThat(tagService.selfCount, equalTo(1)); + assertThat(tagService.doubleCount, equalTo(0)); + assertThat(tagService.squareCount, equalTo(1)); + assertThat(result, contains("pi = 3.14159", "for i = 3, i^2 = 9", "3 3 3.14159")); + } + + @Test + public void shouldNotCallUnusedTags() { + // given + TestTagService tagService = new TestTagService(); + List> tags = tagService.getAvailableTags(); + List messages = Arrays.asList("pi = %PI", "double i = %double"); + + // when + TagReplacer tagReplacer = TagReplacer.newReplacer(tags, messages); + List result1 = tagReplacer.getAdaptedMessages(-4); + List result2 = tagReplacer.getAdaptedMessages(0); + + // then + assertThat(tagService.piCount, equalTo(2)); + assertThat(tagService.selfCount, equalTo(0)); + assertThat(tagService.doubleCount, equalTo(2)); + assertThat(tagService.squareCount, equalTo(0)); + assertThat(result1, contains("pi = 3.14159", "double i = -8")); + assertThat(result2, contains("pi = 3.14159", "double i = 0")); + } + + static final class TestTagService { + int piCount, selfCount, doubleCount, squareCount; + + String pi() { + ++piCount; + return "3.14159"; + } + + String self(int i) { + ++selfCount; + return Integer.toString(i); + } + + String calcDouble(int i) { + ++doubleCount; + return Integer.toString(2 * i); + } + + String calcSquare(int i) { + ++squareCount; + return Integer.toString(i * i); + } + + List> getAvailableTags() { + return Arrays.asList( + createTag("%PI", this::pi), + createTag("%self", this::self), + createTag("%double", this::calcDouble), + createTag("%square", this::calcSquare)); + } + } + +} diff --git a/src/test/java/fr/xephi/authme/util/lazytags/WrappedTagReplacerTest.java b/src/test/java/fr/xephi/authme/util/lazytags/WrappedTagReplacerTest.java new file mode 100644 index 00000000..19915054 --- /dev/null +++ b/src/test/java/fr/xephi/authme/util/lazytags/WrappedTagReplacerTest.java @@ -0,0 +1,76 @@ +package fr.xephi.authme.util.lazytags; + +import fr.xephi.authme.util.lazytags.TagReplacerTest.TestTagService; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link WrappedTagReplacer}. + */ +public class WrappedTagReplacerTest { + + @Test + public void shouldApplyTags() { + // given + TestTagService tagService = new TestTagService(); + List> tags = tagService.getAvailableTags(); + List objects = Arrays.asList( + new SampleClass(3, "pi is %PI"), + new SampleClass(5, "no tags here"), + new SampleClass(7, "i+i = %double")); + + // when + WrappedTagReplacer replacer = new WrappedTagReplacer<>( + tags, objects, SampleClass::getDescription, (o, s) -> new SampleClass(o.number, s)); + List result1 = replacer.getAdaptedItems(8); + List result2 = replacer.getAdaptedItems(1); + + // then + assertThat(tagService.piCount, equalTo(2)); + assertThat(tagService.selfCount, equalTo(0)); + assertThat(tagService.doubleCount, equalTo(2)); + assertThat(tagService.squareCount, equalTo(0)); + assertThat(result1, contains( + sampleClass(3, "pi is 3.14159"), sampleClass(5, "no tags here"), sampleClass(7, "i+i = 16"))); + assertThat(result2, contains( + sampleClass(3, "pi is 3.14159"), sampleClass(5, "no tags here"), sampleClass(7, "i+i = 2"))); + } + + + private static Matcher sampleClass(int number, String description) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(SampleClass item) { + return number == item.number && description.equals(item.description); + } + + @Override + public void describeTo(Description description) { + description.appendText("SampleClass[number=" + number + ";description=" + description + "]"); + } + }; + } + + private static final class SampleClass { + private final int number; + private final String description; + + SampleClass(int number, String description) { + this.number = number; + this.description = description; + } + + String getDescription() { + return description; + } + } +} From a847deac16a2afea12cb4d08a19637cc8b204085 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 26 Feb 2017 14:12:51 +0100 Subject: [PATCH 45/79] #1075 Mail sender - allow to turn off TLS for port 25 --- src/main/java/fr/xephi/authme/AuthMe.java | 7 +++++ .../authme/debug/TestEmailSender.java | 30 ++++++++++++++++--- .../fr/xephi/authme/mail/EmailService.java | 15 ---------- .../fr/xephi/authme/mail/SendMailSSL.java | 12 ++++---- .../settings/properties/EmailSettings.java | 4 +++ 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 128e9693..21b9bcb5 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -30,6 +30,7 @@ import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.MigrationService; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -269,6 +270,12 @@ public class AuthMe extends JavaPlugin { && settings.getProperty(PluginSettings.SESSIONS_ENABLED)) { ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!"); } + + // Use TLS property only affects port 25 + if (!settings.getProperty(EmailSettings.PORT25_USE_TLS) + && settings.getProperty(EmailSettings.SMTP_PORT) != 25) { + ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25"); + } } /** diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java index 2a994246..b0abcd55 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -1,9 +1,13 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; -import fr.xephi.authme.mail.EmailService; +import fr.xephi.authme.mail.SendMailSSL; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; import org.bukkit.ChatColor; +import org.bukkit.Server; import org.bukkit.command.CommandSender; import javax.inject.Inject; @@ -18,7 +22,10 @@ class TestEmailSender implements DebugSection { private DataSource dataSource; @Inject - private EmailService emailService; + private SendMailSSL sendMailSSL; + + @Inject + private Server server; @Override @@ -33,7 +40,7 @@ class TestEmailSender implements DebugSection { @Override public void execute(CommandSender sender, List arguments) { - if (!emailService.hasAllInformation()) { + if (!sendMailSSL.hasAllInformation()) { sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + "for sending emails. Please check your config.yml"); return; @@ -43,7 +50,7 @@ class TestEmailSender implements DebugSection { // getEmail() takes care of informing the sender of the error if email == null if (email != null) { - boolean sendMail = emailService.sendTestEmail(email); + boolean sendMail = sendTestEmail(email); if (sendMail) { sender.sendMessage("Test email sent to " + email + " with success"); } else { @@ -75,4 +82,19 @@ class TestEmailSender implements DebugSection { return null; } } + + private boolean sendTestEmail(String email) { + HtmlEmail htmlEmail; + try { + htmlEmail = sendMailSSL.initializeMail(email); + } catch (EmailException e) { + ConsoleLogger.logException("Failed to create email for sample email:", e); + return false; + } + + htmlEmail.setSubject("AuthMe test email"); + String message = "Hello there!
This is a sample email sent to you from a Minecraft server (" + + server.getName() + ") via /authme debug mail. If you're seeing this, sending emails should be fine."; + return sendMailSSL.sendEmail(message, htmlEmail); + } } diff --git a/src/main/java/fr/xephi/authme/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java index 46343369..cb7c86de 100644 --- a/src/main/java/fr/xephi/authme/mail/EmailService.java +++ b/src/main/java/fr/xephi/authme/mail/EmailService.java @@ -94,21 +94,6 @@ public class EmailService { return sendMailSSL.sendEmail(message, htmlEmail); } - public boolean sendTestEmail(String email) { - HtmlEmail htmlEmail; - try { - htmlEmail = sendMailSSL.initializeMail(email); - } catch (EmailException e) { - ConsoleLogger.logException("Failed to create email for sample email:", e); - return false; - } - - htmlEmail.setSubject("AuthMe test email"); - String message = "Hello there!
This is a sample email sent to you from a Minecraft server (" - + serverName + ") via /authme debug mail. If you're seeing this, sending emails should be fine."; - return sendMailSSL.sendEmail(message, htmlEmail); - } - private File generateImage(String name, String newPass) throws IOException { ImageGenerator gen = new ImageGenerator(newPass); File file = new File(dataFolder, name + "_new_pass.jpg"); diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index 2b482b26..344c39c9 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -24,12 +24,8 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; */ public class SendMailSSL { - private final Settings settings; - @Inject - SendMailSSL(Settings settings) { - this.settings = settings; - } + private Settings settings; /** * Returns whether all necessary settings are set for sending mails. @@ -115,8 +111,10 @@ public class SendMailSSL { } break; case 25: - email.setStartTLSEnabled(true); - email.setSSLCheckServerIdentity(true); + if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) { + email.setStartTLSEnabled(true); + email.setSSLCheckServerIdentity(true); + } break; case 465: email.setSslSmtpPort(Integer.toString(port)); diff --git a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java index 561ce511..3a9ede5d 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java @@ -19,6 +19,10 @@ public class EmailSettings implements SettingsHolder { public static final Property SMTP_PORT = newProperty("Email.mailPort", 465); + @Comment("Only affects port 25: enable TLS/STARTTLS?") + public static final Property PORT25_USE_TLS = + newProperty("Email.useTls", true); + @Comment("Email account which sends the mails") public static final Property MAIL_ACCOUNT = newProperty("Email.mailAccount", ""); From 8a7c8c36f264d143ca57485818b5de2d66387c65 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 26 Feb 2017 14:18:18 +0100 Subject: [PATCH 46/79] List all subcommands if debug section is unknown --- .../executable/authme/debug/DebugCommand.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index 9d67da39..c910cb92 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -26,20 +26,23 @@ public class DebugCommand implements ExecutableCommand { @Override public void executeCommand(CommandSender sender, List arguments) { - if (arguments.isEmpty()) { + DebugSection debugSection = getDebugSection(arguments); + if (debugSection == null) { sender.sendMessage("Available sections:"); getSections().values() .forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription())); } else { - DebugSection debugSection = getSections().get(arguments.get(0).toLowerCase()); - if (debugSection == null) { - sender.sendMessage("Unknown subcommand"); - } else { - debugSection.execute(sender, arguments.subList(1, arguments.size())); - } + debugSection.execute(sender, arguments.subList(1, arguments.size())); } } + private DebugSection getDebugSection(List arguments) { + if (arguments.isEmpty()) { + return null; + } + return getSections().get(arguments.get(0).toLowerCase()); + } + // Lazy getter private Map getSections() { if (sections == null) { From bf3d6b0e7c22b29e27387a59ee13ec2d5eb491c1 Mon Sep 17 00:00:00 2001 From: Jacek Maciejak Date: Mon, 27 Feb 2017 19:04:54 +0100 Subject: [PATCH 47/79] Update messages_pl.yml (#221) * Update messages_pl.yml * Update messages_pl.yml Fix --- src/main/resources/messages/messages_pl.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 549efb9e..35622e5d 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -87,7 +87,7 @@ email_send_failure: 'Nie mozna wyslac emaila. Skontaktuj sie z administracja.' show_no_email: '&2Nie posiadasz adresu email przypisanego do tego konta.' add_email: '&cProsze dodac swoj email: /email add twojEmail powtorzEmail' recovery_email: '&cZapomniales hasla? Prosze uzyj komendy /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cEmail zostal wyslany, musisz poczekac %time przed wyslaniem nastepnego.' # Captcha usage_captcha: '&cWpisz: /captcha ' @@ -95,11 +95,11 @@ wrong_captcha: '&cZly kod, prosze wpisac: /captcha THE_CAPTCHA' valid_captcha: '&cTwoj kod jest nieprawidlowy!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'sekundy' +seconds: 'sekund' +minute: 'minuty' +minutes: 'minut' +hour: 'godziny' +hours: 'godzin' +day: 'lata' +days: 'lat' From 41b6c34b6bcaef8d00f990c6ac23727762bbbddd Mon Sep 17 00:00:00 2001 From: Maxetto Date: Mon, 27 Feb 2017 19:50:30 +0100 Subject: [PATCH 48/79] Transform values into constants (cherry picked from commit 20d83e5 et al.) --- src/main/java/fr/xephi/authme/data/limbo/LimboCache.java | 4 ++-- src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java | 3 +++ .../java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java | 4 ++-- src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java index b6fa2944..26883ca9 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java @@ -80,10 +80,10 @@ public class LimboCache { float flySpeed = data.getFlySpeed(); // Reset the speed value if it was 0 if (walkSpeed < 0.01f) { - walkSpeed = 0.2f; + walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; } if (flySpeed < 0.01f) { - flySpeed = 0.2f; + flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; } player.setWalkSpeed(walkSpeed); player.setFlySpeed(flySpeed); diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java index b551deed..45fcd7ad 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java @@ -10,6 +10,9 @@ import org.bukkit.scheduler.BukkitTask; */ public class LimboPlayer { + public static final float DEFAULT_WALK_SPEED = 0.2f; + public static final float DEFAULT_FLY_SPEED = 0.1f; + private final boolean canFly; private final boolean operator; private final String group; diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java index dd2790bc..1f077d2a 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java @@ -148,8 +148,8 @@ public class LimboPlayerStorage { String group = ""; boolean operator = false; boolean canFly = false; - float walkSpeed = 0.2f; - float flySpeed = 0.2f; + float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; + float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; JsonElement e; if ((e = jsonObject.getAsJsonObject("location")) != null) { diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java index 152f2e35..8d307c25 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java @@ -143,8 +143,8 @@ public class LimboCacheTest { limboCache.restoreData(player); // then - verify(player).setWalkSpeed(0.2f); - verify(player).setFlySpeed(0.2f); + verify(player).setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED); + verify(player).setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); } @Test From 718520671864e6f0014486198cad7678e5b4d2c9 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 27 Feb 2017 22:45:46 +0100 Subject: [PATCH 49/79] Minor - use replace instead of replaceAll for non-regex replacements --- .../java/fr/xephi/authme/process/login/AsynchronousLogin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index db85b858..6fb8ff85 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -269,8 +269,8 @@ public class AsynchronousLogin implements AsynchronousProcess { } bukkitService.dispatchConsoleCommand(command - .replaceAll("%playername%", player.getName()) - .replaceAll("%playerip%", ip) + .replace("%playername%", player.getName()) + .replace("%playerip%", ip) ); } From d450d7d8284d4dfade7eb742abde8f2f62a0bafd Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 28 Feb 2017 19:27:14 +0100 Subject: [PATCH 50/79] #1114 Update Turkish texts by @smt287 --- docs/translations.md | 9 ++-- src/main/resources/messages/messages_tr.yml | 46 ++++++++++----------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/translations.md b/docs/translations.md index a4bc104b..70c26d8b 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -23,12 +23,12 @@ Code | Language | Translated |   [ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | bar [lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | bar [nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | bar -[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 89% | bar +[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar [pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 77% | bar [ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% | bar [ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% | bar [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | bar -[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 72% | bar +[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | bar [uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% | bar [vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 89% | bar [zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% | bar @@ -36,6 +36,7 @@ Code | Language | Translated |   [zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | bar [zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | bar + --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Feb 25 21:59:17 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Feb 28 19:25:18 CET 2017 diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index a3ba1f26..1f3795af 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -2,7 +2,7 @@ reg_msg: '&3Lutfen kayit komutunu kullanin "/register "' usage_reg: '&cKullanim: /register ' reg_only: '&4Sunucuya kayit sadece internet uzerinden yapilmakta! Lutfen http://ornek.com sitesini kayit icin ziyaret edin!' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +kicked_admin_registered: 'Bir yetkili seni kayit etti; tekrardan giris yap' registered: '&2Basariyla kaydoldun!' reg_disabled: '&cOyun icin kayit olma kapatildi!' user_regged: '&cSenin adinda daha once birisi kaydolmus!' @@ -11,7 +11,7 @@ user_regged: '&cSenin adinda daha once birisi kaydolmus!' password_error: '&cSifre eslesmiyor, tekrar deneyin!' password_error_nick: '&cSifrenize adinizi koyamazsiniz, lutfen farkli bir sifre secin...' password_error_unsafe: '&cSectiginiz sifre guvenli degil, lutfen farkli bir sifre secin...' -# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' +password_error_chars: '&4Sifrenizde izin verilmeyen karakterler bulunmakta. Izin verilen karakterler: REG_EX' pass_len: '&cSenin sifren ya cok kisa yada cok uzun! Lutfen farkli birsey dene!' # Login @@ -23,10 +23,10 @@ timeout: '&4Giris izni icin verilen zaman suresini astigin icin sunucudan atildi # Errors unknown_user: '&cBu oyuncu kayitli degil!' -# TODO denied_command: '&cIn order to use this command you must be authenticated!' -# TODO denied_chat: '&cIn order to chat you must be authenticated!' +denied_command: '&cSuanda bu komutu kullanamazsin!' +denied_chat: '&cSuanda sohbeti kullanamazsin!' not_logged_in: '&cGiris yapmadin!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&cBir cok kez yanlis giris yaptiginiz icin gecici olarak banlandiniz.' max_reg: '&cSen maksimum kayit sinirini astin (%reg_count/%max_acc %reg_names)!' no_perm: '&4Bunu yapmak icin iznin yok!' error: '&4Beklenmedik bir hata olustu, yetkili ile iletisime gecin!' @@ -40,11 +40,11 @@ antibot_auto_disabled: '&2[AntiBotServis] AntiBot, %m dakika sonra deaktif edile # Other messages unregistered: '&cKayit basariyla kaldirildi!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: 'Sen %count hesaba sahipsin:' +accounts_owned_other: 'Oyuncu %name %count hesaba sahip:' two_factor_create: '&2Gizli kodunuz %code. Buradan test edebilirsin, %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' +recovery_code_sent: 'Sifre sifirlama kodu eposta adresinize gonderildi.' +recovery_code_incorrect: 'Kod dogru degil! Kullanim "/email recovery [eposta]" ile yeni bir kod olustur' vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!' usage_unreg: '&cKullanim: /unregister ' pwd_changed: '&2Sifre basariyla degistirildi!' @@ -65,7 +65,7 @@ not_owner_error: 'Bu hesabin sahibi degilsin. Lutfen farkli bir isim sec!' kick_fullserver: '&4Sunucu suanda dolu, daha sonra tekrar deneyin!' same_nick: '&4Senin isminde bir oyuncu suncuda bulunmakta!' invalid_name_case: 'Oyuna %valid isminde katilmalisin. %invalid ismini kullanarak katilamazsin.' -# TODO same_ip_online: 'A player with the same IP is already in game!' +same_ip_online: 'Oyunda sizin ipnizden giren biri bulunmakta!' # Email usage_email_add: '&cKullanim: /email add ' @@ -79,14 +79,14 @@ email_confirm: '&cLutfen tekrar epostanizi giriniz!' email_changed: '&2Epostaniz basariyla degistirildi!' email_send: '&2Sifreniz epostaniza gonderildi! Lutfen eposta kutunuzu kontrol edin!' email_exists: '&cSifreniz zaten epostanize gonderildi! Bunu iptal etmek veya yeni bir sifre gondermek icin assagidaki komutu kullanabilirsin:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2Suanki eposta adresin: &f%email' +incomplete_email_settings: 'Hata: Gonderilen epostada bazi ayarlar tamamlanmis degil. Yetkili ile iletisime gec.' email_already_used: '&4Eposta adresi zaten kullaniliyor.' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' +email_send_failure: 'Eposta gonderilemedi. Yetkili ile iletisime gec.' +show_no_email: '&2Bu hesapla iliskili bir eposta bulunmuyor.' add_email: '&3Lutfen hesabinize eposta adresinizi komut ile ekleyin "/email add "' recovery_email: '&3Sifreni mi unuttun ? Komut kullanarak ogrenebilirsin "/email recovery "' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cKisa bir sure once eposta gonderildi. Yeni bir eposta almak icin %time beklemelisin.' # Captcha usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captcha "' @@ -94,11 +94,11 @@ wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" so valid_captcha: '&2Guvenlik kodu dogrulandi!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'saniye' +seconds: 'saniyeler' +minute: 'dakika' +minutes: 'dakikalar' +hour: 'saat' +hours: 'saatler' +day: 'gun' +days: 'gunler' From ebfada155704597797f6d648d5fd43802291c372 Mon Sep 17 00:00:00 2001 From: Twonox Date: Thu, 2 Mar 2017 08:21:06 +0100 Subject: [PATCH 51/79] Update messages_fr.yml (#222) --- src/main/resources/messages/messages_fr.yml | 54 ++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index dd01ccc6..9070db19 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -1,7 +1,7 @@ # Traduction par: André & Twonox -# Pour afficher une guillemet, mettez deux guillemets consécutivement (ex: "J''ai" au lieu de "J'ai"). -# Pour passer à la ligne, vous pouvez utiliser "%nl%" +# Pour afficher une apostrophe, vous devez en mettre deux consécutivement (ex: «J''ai» au lieu de «J'ai») +# Pour passer à la ligne, utilisez: %nl% # Inscription reg_msg: '&cPour vous inscrire, utilisez "/register "' @@ -10,7 +10,7 @@ reg_only: 'Seul les joueurs enregistrés sont admis!%nl%Veuillez vous rendre sur kicked_admin_registered: 'Un admin vient de vous inscrire, veuillez vous reconnecter.' registered: '&aInscription effectué !' reg_disabled: '&cL''inscription est désactivé.' -user_regged: '&cCet utilisateur est déjà inscrit.' +user_regged: '&cUtilisateur déjà inscrit.' # Erreurs MDP pour l'inscription password_error: '&cLe mot de passe de confirmation ne correspond pas.' @@ -27,7 +27,7 @@ login_msg: '&cPour vous connecter, utilisez "/login "' timeout: 'Vous avez été expulsé car vous êtes trop lent pour vous enregistrer/connecter !' # Erreurs -unknown_user: '&cUtilisateur introuvable.' +unknown_user: '&cUtilisateur non-inscrit.' denied_command: '&cVous devez être connecté pour pouvoir utiliser cette commande.' denied_chat: '&cVous devez être connecté pour pouvoir écrire dans le chat.' not_logged_in: '&cUtilisateur non connecté !' @@ -73,37 +73,37 @@ invalid_name_case: 'Veuillez vous connecter avec "%valid" et non pas avec "%inva same_ip_online: 'Un joueur avec la même adresse IP joue déjà !' # Email -usage_email_add: '&fUsage: /email add ' +usage_email_add: '&fUsage: /email add ' usage_email_change: '&fUsage: /email change ' usage_email_recovery: '&fUsage: /email recovery ' -new_email_invalid: '[AuthMe] Nouvel email invalide !' -old_email_invalid: '[AuthMe] Ancien email invalide !' -email_invalid: '[AuthMe] Email invalide' -email_added: '[AuthMe] Email ajouté !' -email_confirm: '[AuthMe] Confirmez votre email !' -email_changed: '[AuthMe] Email changé !' -email_send: '[AuthMe] Email de récupération envoyé !' -email_exists: '&cUn email de restauration a déjà été envoyé ! Vous pouvez le jeter et vous en faire envoyez un nouveau en utilisant :' +new_email_invalid: '&cNouvel email invalide !' +old_email_invalid: '&cAncien email invalide !' +email_invalid: '&cL''email inscrit est invalide !' +email_added: '&aEmail enregistré. En cas de perte de MDP, faites "/email recover "' +email_confirm: '&cLa confirmation de l''email est manquante ou éronnée.' +email_changed: '&aVotre email a été mis à jour.' +email_send: '&aEmail de récupération envoyé !' +email_exists: '&cUn email de récupération a déjà été envoyé ! Vous pouvez le jeter et vous en faire envoyez un nouveau en utilisant :' email_show: '&2Votre adresse email actuelle est: &f%email' incomplete_email_settings: '&cErreur : Tous les paramètres requis ne sont pas présent pour l''envoi de mail, veuillez contacter un admin.' email_already_used: '&cCette adresse email est déjà utilisée !' email_send_failure: '&cL''email n''a pas pu être envoyé. Veuillez contacter un admin.' -show_no_email: 'Vous n''avez aucune adresse email enregistré sur votre compte.' -add_email: '&cMerci d''ajouter votre email : /email add ' -recovery_email: '&cVous avez oublié votre Mot de Passe? Utilisez /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +show_no_email: '&c&oVous n''avez aucune adresse mail enregistré sur votre compte.' +add_email: '&cRajoutez un email de récupération: /email add ' +recovery_email: '&cVous avez oublié votre Mot de Passe? Utilisez "/email recovery "' +email_cooldown_error: '&cUn email de récupération a déjà été envoyé récemment. Veuillez attendre %time pour le demander de nouveau.' # Captcha usage_captcha: '&cTrop de tentatives de connexion échouées, utilisez: /captcha ' -wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau : /captcha THE_CAPTCHA' +wrong_captcha: '&cCaptcha incorrect, écrivez de nouveau: /captcha THE_CAPTCHA' valid_captcha: '&aCaptché validé! Veuillez maintenant vous connecter.' -# Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +# Unités de temps +second: 'seconde' +seconds: 'secondes' +minute: 'minute' +minutes: 'minutes' +hour: 'heure' +hours: 'heures' +day: 'jour' +days: 'jours' From f9ebf63dcf22373a747277967ea09a52a3857a94 Mon Sep 17 00:00:00 2001 From: Twonox Date: Thu, 2 Mar 2017 08:25:46 +0100 Subject: [PATCH 52/79] Update help_fr.yml (#223) --- src/main/resources/messages/help_fr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/messages/help_fr.yml b/src/main/resources/messages/help_fr.yml index 318b451e..7d53ae79 100644 --- a/src/main/resources/messages/help_fr.yml +++ b/src/main/resources/messages/help_fr.yml @@ -1,9 +1,9 @@ -# Traduction des messages d'aide d'AuthMe (pour la commande "/authme help ()" ou "/register help" par exemple) +# Traduction des messages d'aide d'AuthMe (par exemple, pour les messages de "/authme help ()" ou de "/register help") # ------------------------------------------------------- # Liste de texte dans les sections d'aide common: - header: '==========[ AIDE & INFOS - AuthMe ]==========' + header: '==========[ AuthMe - AIDE & INFOS ]==========' optional: 'Optionnel' hasPermission: 'Vous avez la permission' noPermission: 'Pas de permission' From 1e9ba53471ab20c862faeac9071ff23ad602acde Mon Sep 17 00:00:00 2001 From: Twonox Date: Thu, 2 Mar 2017 08:26:29 +0100 Subject: [PATCH 53/79] Update messages_tr.yml (#224) --- src/main/resources/messages/messages_tr.yml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 1f3795af..3431e99e 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -1,4 +1,4 @@ -# Registration +# Kayit mesajlari reg_msg: '&3Lutfen kayit komutunu kullanin "/register "' usage_reg: '&cKullanim: /register ' reg_only: '&4Sunucuya kayit sadece internet uzerinden yapilmakta! Lutfen http://ornek.com sitesini kayit icin ziyaret edin!' @@ -7,21 +7,21 @@ registered: '&2Basariyla kaydoldun!' reg_disabled: '&cOyun icin kayit olma kapatildi!' user_regged: '&cSenin adinda daha once birisi kaydolmus!' -# Password errors on registration +# Kayit aninda sifre hatalari password_error: '&cSifre eslesmiyor, tekrar deneyin!' password_error_nick: '&cSifrenize adinizi koyamazsiniz, lutfen farkli bir sifre secin...' password_error_unsafe: '&cSectiginiz sifre guvenli degil, lutfen farkli bir sifre secin...' password_error_chars: '&4Sifrenizde izin verilmeyen karakterler bulunmakta. Izin verilen karakterler: REG_EX' pass_len: '&cSenin sifren ya cok kisa yada cok uzun! Lutfen farkli birsey dene!' -# Login +# Oturuma giris usage_log: '&cKullanim: /login ' wrong_pwd: '&cYanlis sifre!' login: '&2Giris basarili!' login_msg: '&cLutfen giris komutunu kullanin "/login "' timeout: '&4Giris izni icin verilen zaman suresini astigin icin sunucudan atildin, tekrar deneyin!' -# Errors +# Hata mesajlari unknown_user: '&cBu oyuncu kayitli degil!' denied_command: '&cSuanda bu komutu kullanamazsin!' denied_chat: '&cSuanda sohbeti kullanamazsin!' @@ -38,7 +38,7 @@ kick_antibot: 'AntiBot koruma modu aktif! Birkac dakika sonra tekrar girmeyi den antibot_auto_enabled: '&4[AntiBotServis] Saldiri oldugu icin AntiBot aktif edildi!' antibot_auto_disabled: '&2[AntiBotServis] AntiBot, %m dakika sonra deaktif edilecek!' -# Other messages +# Baska mesajlar unregistered: '&cKayit basariyla kaldirildi!' accounts_owned_self: 'Sen %count hesaba sahipsin:' accounts_owned_other: 'Oyuncu %name %count hesaba sahip:' @@ -53,11 +53,11 @@ logout: '&2Basariyla cikis yaptin!' reload: '&2Ayarlar ve veritabani yenilendi!' usage_changepassword: '&cKullanim: /changepassword ' -# Session messages +# Otomatik giris invalid_session: '&cIP adresin degistirildi ve oturum suren doldu!' -valid_session: '&2Oturum icin yeniden giris gerekiyor.' +valid_session: '&2Oturuma girisiniz otomatikmen yapilmistir.' -# Error messages when joining +# Servore giris aninda hata mesajlari name_len: '&4Senin ismin ya cok kisa yada cok uzun!' regex: '&4Senin isminde uygunsuz karakterler bulunmakta. Izin verilen karakterler: REG_EX' country_banned: '&4Senin bolgen sunucudan yasaklandi!' @@ -93,12 +93,12 @@ usage_captcha: '&3Giris yapmak icin guvenlik kodunu komut yazarak girin "/captch wrong_captcha: '&cYanlis guvenlik kodu, kullanim sekli "/captcha THE_CAPTCHA" sohbete yazin!' valid_captcha: '&2Guvenlik kodu dogrulandi!' -# Time units +# Zaman birimleri second: 'saniye' -seconds: 'saniyeler' +seconds: 'saniye' minute: 'dakika' -minutes: 'dakikalar' +minutes: 'dakika' hour: 'saat' -hours: 'saatler' +hours: 'saat' day: 'gun' -days: 'gunler' +days: 'gun' From 283b5aebb74489487871fdfab54b604b4b344ef7 Mon Sep 17 00:00:00 2001 From: Maxetto Date: Thu, 2 Mar 2017 11:04:56 +0100 Subject: [PATCH 54/79] [Messages_IT] Email recovery delay (#225) Plus some adjustements since emails can now contain recovery codes instead of new passwords. --- src/main/resources/messages/messages_it.yml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index ede27690..989b555b 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -79,28 +79,28 @@ email_invalid: '&cL''indirizzo email inserito non è valido, riprova!' email_added: '&2Indirizzo email aggiunto correttamente al tuo account!' email_confirm: '&cPer favore, conferma il tuo indirizzo email!' email_changed: '&2Indirizzo email cambiato correttamente!' -email_send: '&2Una email contenente la tua nuova password è stata appena inviata al tuo indirizzo email!' -email_exists: '&cL''email contenente la tua nuova password è già stata inviata! Se vuoi, puoi annullarla e mandarne un''altra con il seguente comando:' +email_send: '&2Una email di recupero è stata appena inviata al tuo indirizzo email!' +email_exists: '&cUna email di recupero ti è già stata inviata! Se vuoi, puoi annullarla e richiederne un''altra con il seguente comando:' email_show: '&2Il tuo indirizzo email al momento è: &f%email' incomplete_email_settings: 'Errore: non tutte le impostazioni richieste per inviare le email sono state impostate. Per favore contatta un amministratore.' email_already_used: '&4L''indirizzo email inserito è già in uso' -email_send_failure: 'Non è stato possibile inviare l''email contenente la tua nuova password. Per favore contatta un amministratore.' +email_send_failure: 'Non è stato possibile inviare l''email di recupero. Per favore contatta un amministratore.' show_no_email: '&2Al momento non hai nessun indirizzo email associato al tuo account.' add_email: '&3Per poter recuperare la password in futuro, aggiungi un indirizzo email al tuo account con il comando: /email add ' recovery_email: '&3Hai dimenticato la tua password? Puoi recuperarla eseguendo il comando: /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cUna email di recupero ti è già stata inviata recentemente. Devi attendere %time prima di poterne richiedere una nuova.' # Captcha usage_captcha: '&3Per poterti autenticare devi risolvere un captcha, per favore scrivi: /captcha ' wrong_captcha: '&cCaptcha sbagliato, per favore riprova scrivendo: "/captcha THE_CAPTCHA" in chat!' valid_captcha: '&2Il captcha inserito è valido!' -# Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +# Unità di tempo +second: 'secondo' +seconds: 'secondi' +minute: 'minuto' +minutes: 'minuti' +hour: 'ora' +hours: 'ore' +day: 'giorno' +days: 'giorni' From c079f5f3d56ace12f5592693450ca8cb9d7fd625 Mon Sep 17 00:00:00 2001 From: Den Date: Sat, 4 Mar 2017 03:09:43 +0300 Subject: [PATCH 55/79] Update messages_ru.yml (#227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New messages have been translated. Uits of time reduced, as can be conjugated differently. ================================== Были переведены новые сообщения. Единицы времени сокращены, так как могут спрягаться по разному. --- src/main/resources/messages/messages_ru.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 11f55835..ed4e1729 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -86,19 +86,19 @@ email_send_failure: 'Письмо не може быть отправлено. show_no_email: '&2В данный момент к вашему аккаунте не привязана электронная почта.' add_email: '&cДобавьте свой email: &e/email add <Ваш Email> <Ваш Email>' recovery_email: '&cЗабыли пароль? Используйте &e/email recovery <Ваш Email>' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cЭлектронное письмо было отправлено недавно. Пожалуйста, подождите %time прежде чем отправить новое письмо.' # Каптча usage_captcha: '&cВы должны ввести код, используйте: &e/captcha ' wrong_captcha: '&cНеверный код, используйте: &e/captcha THE_CAPTCHA' valid_captcha: '&2Вы успешно ввели код!' -# Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +# Единицы времени +second: 'сек.' +seconds: 'сек.' +minute: 'мин.' +minutes: 'мин.' +hour: 'ч.' +hours: 'ч.' +day: 'дн.' +days: 'дн.' From 009d82c0a944c1ae278d28deff1403d9a927cf72 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 4 Mar 2017 17:52:08 +0100 Subject: [PATCH 56/79] #1120 Use empty "realname" when converting from flatfile - FlatFile doesn't store the "realname" - all names are always in all-lowercase. Converting from flatfile to other data source should therefore not take over an auth's realname - Adjust sample flatfile file to only have all-lowercase usernames --- .../converter/AbstractDataSourceConverter.java | 12 +++++++++++- .../datasource/converter/ForceFlatToSqlite.java | 8 ++++++++ .../authme/datasource/FlatFileIntegrationTest.java | 8 ++++---- .../datasource/converter/ForceFlatToSqliteTest.java | 6 +++--- .../fr/xephi/authme/datasource/flatfile-test.txt | 12 ++++++------ 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java index d2be7804..8ca50625 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java @@ -37,7 +37,7 @@ public abstract class AbstractDataSourceConverter implemen // which is never the case when a converter is launched from the /authme converter command. @Override public void execute(CommandSender sender) { - if (!destinationType.equals(destination.getType())) { + if (destinationType != destination.getType()) { if (sender != null) { sender.sendMessage("Please configure your connection to " + destinationType + " and re-run this command"); @@ -59,6 +59,7 @@ public abstract class AbstractDataSourceConverter implemen if (destination.isAuthAvailable(auth.getNickname())) { skippedPlayers.add(auth.getNickname()); } else { + adaptPlayerAuth(auth); destination.saveAuth(auth); destination.updateQuitLoc(auth); } @@ -72,6 +73,15 @@ public abstract class AbstractDataSourceConverter implemen + " to " + destinationType); } + /** + * Adapts the PlayerAuth from the source before it is saved in the destination. + * + * @param auth the auth from the source + */ + protected void adaptPlayerAuth(PlayerAuth auth) { + // noop + } + /** * @return the data source to convert from * @throws Exception during initialization of source diff --git a/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java b/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java index a52b216b..1c67061f 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java @@ -1,5 +1,6 @@ package fr.xephi.authme.datasource.converter; +import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.FlatFile; @@ -25,4 +26,11 @@ public class ForceFlatToSqlite extends AbstractDataSourceConverter { public FlatFile getSource() { return source; } + + @Override + protected void adaptPlayerAuth(PlayerAuth auth) { + // Issue #1120: FlatFile returns PlayerAuth objects with realname = lower-case name all the time. + // We don't want to take this over into the new data source. + auth.setRealName("Player"); + } } diff --git a/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java index 7e0bb1a5..102584ed 100644 --- a/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/datasource/FlatFileIntegrationTest.java @@ -61,13 +61,13 @@ public class FlatFileIntegrationTest { // then assertThat(authList, hasSize(7)); - assertThat(getName("bobby", authList), hasAuthBasicData("bobby", "Bobby", "your@email.com", "123.45.67.89")); + assertThat(getName("bobby", authList), hasAuthBasicData("bobby", "bobby", "your@email.com", "123.45.67.89")); assertThat(getName("bobby", authList), hasAuthLocation(1.05, 2.1, 4.2, "world")); assertThat(getName("bobby", authList).getPassword(), equalToHash("$SHA$11aa0706173d7272$dbba966")); - assertThat(getName("twofields", authList), hasAuthBasicData("twofields", "twoFields", "your@email.com", "127.0.0.1")); + assertThat(getName("twofields", authList), hasAuthBasicData("twofields", "twofields", "your@email.com", "127.0.0.1")); assertThat(getName("twofields", authList).getPassword(), equalToHash("hash1234")); - assertThat(getName("threefields", authList), hasAuthBasicData("threefields", "threeFields", "your@email.com", "33.33.33.33")); - assertThat(getName("fourfields", authList), hasAuthBasicData("fourfields", "fourFields", "your@email.com", "4.4.4.4")); + assertThat(getName("threefields", authList), hasAuthBasicData("threefields", "threefields", "your@email.com", "33.33.33.33")); + assertThat(getName("fourfields", authList), hasAuthBasicData("fourfields", "fourfields", "your@email.com", "4.4.4.4")); assertThat(getName("fourfields", authList).getLastLogin(), equalTo(404040404L)); assertThat(getName("sevenfields", authList), hasAuthLocation(7.7, 14.14, 21.21, "world")); assertThat(getName("eightfields", authList), hasAuthLocation(8.8, 17.6, 26.4, "eightworld")); diff --git a/src/test/java/fr/xephi/authme/datasource/converter/ForceFlatToSqliteTest.java b/src/test/java/fr/xephi/authme/datasource/converter/ForceFlatToSqliteTest.java index 3ec28a7b..556f58ef 100644 --- a/src/test/java/fr/xephi/authme/datasource/converter/ForceFlatToSqliteTest.java +++ b/src/test/java/fr/xephi/authme/datasource/converter/ForceFlatToSqliteTest.java @@ -63,11 +63,11 @@ public class ForceFlatToSqliteTest { ArgumentCaptor authCaptor = ArgumentCaptor.forClass(PlayerAuth.class); verify(dataSource, times(7)).saveAuth(authCaptor.capture()); List auths = authCaptor.getAllValues(); - assertThat(auths, hasItem(hasAuthBasicData("bobby", "Bobby", "your@email.com", "123.45.67.89"))); + assertThat(auths, hasItem(hasAuthBasicData("bobby", "Player", "your@email.com", "123.45.67.89"))); assertThat(auths, hasItem(hasAuthLocation(1.05, 2.1, 4.2, "world"))); - assertThat(auths, hasItem(hasAuthBasicData("user", "user", "user@example.org", "34.56.78.90"))); + assertThat(auths, hasItem(hasAuthBasicData("user", "Player", "user@example.org", "34.56.78.90"))); assertThat(auths, hasItem(hasAuthLocation(124.1, 76.3, -127.8, "nether"))); - assertThat(auths, hasItem(hasAuthBasicData("eightfields", "eightFields", "your@email.com", "6.6.6.66"))); + assertThat(auths, hasItem(hasAuthBasicData("eightfields", "Player", "your@email.com", "6.6.6.66"))); assertThat(auths, hasItem(hasAuthLocation(8.8, 17.6, 26.4, "eightworld"))); } diff --git a/src/test/resources/fr/xephi/authme/datasource/flatfile-test.txt b/src/test/resources/fr/xephi/authme/datasource/flatfile-test.txt index da5a1312..d0ac4aa5 100644 --- a/src/test/resources/fr/xephi/authme/datasource/flatfile-test.txt +++ b/src/test/resources/fr/xephi/authme/datasource/flatfile-test.txt @@ -1,7 +1,7 @@ -Bobby:$SHA$11aa0706173d7272$dbba966:123.45.67.89:1449136800:1.05:2.1:4.2:world:your@email.com +bobby:$SHA$11aa0706173d7272$dbba966:123.45.67.89:1449136800:1.05:2.1:4.2:world:your@email.com user:b28c32f624a4eb161d6adc9acb5bfc5b:34.56.78.90:1453242857:124.1:76.3:-127.8:nether:user@example.org -twoFields:hash1234 -threeFields:hash369:33.33.33.33 -fourFields:$hash$4444:4.4.4.4:404040404 -sevenFields:hash7749:5.5.5.55:1414141414:7.7:14.14:21.21 -eightFields:hash8168:6.6.6.66:1234567888:8.8:17.6:26.4:eightworld +twofields:hash1234 +threefields:hash369:33.33.33.33 +fourfields:$hash$4444:4.4.4.4:404040404 +sevenfields:hash7749:5.5.5.55:1414141414:7.7:14.14:21.21 +eightfields:hash8168:6.6.6.66:1234567888:8.8:17.6:26.4:eightworld From a64f758ee9ae912c342ec64f4aaee81ab0a6a11b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 4 Mar 2017 20:49:11 +0100 Subject: [PATCH 57/79] Add config files for Code Climate - https://codeclimate.com - Includes customized Checkstyle configuration for AuthMe --- .checkstyle.xml | 164 +++++++++++++++++++++++++++++++++++++++++++++++ .codeclimate.yml | 28 ++++++++ 2 files changed, 192 insertions(+) create mode 100644 .checkstyle.xml create mode 100644 .codeclimate.yml diff --git a/.checkstyle.xml b/.checkstyle.xml new file mode 100644 index 00000000..d8f9076d --- /dev/null +++ b/.checkstyle.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..ee496167 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,28 @@ +engines: + duplication: + enabled: true + config: + languages: + - java + - php + checkstyle: + enabled: true + channel: beta + config: '.checkstyle.xml' + pmd: + enabled: true + channel: beta + checks: + AvoidUsingHardCodedIP: + enabled: false +ratings: + paths: + # Check only production files + - 'src/main/java/**' +exclude_paths: +# Exclude code from third-party sources +- 'src/main/java/fr/xephi/authme/mail/OAuth2Provider.java' +- 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java' +- 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java' +- 'src/main/java/fr/xephi/authme/security/crypts/BCryptService.java' +- 'src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java' From ed9c5ef8a72ab3f4d0ad09acb5f8428eb9a51571 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 10:19:11 +0100 Subject: [PATCH 58/79] Readme: replace metrics with bstats, add code climate badge --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c9c717c7..2138ed4f 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,14 @@ - Project status: - Dependencies: [![Dependencies status](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d/badge.svg)](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d) - Test coverage: [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master) + - Code climate: [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded) - Development resources: - JavaDocs - Maven Repository -#####Statistics: - -McStats: http://mcstats.org/plugin/AuthMe - - - - - - +- Statistics: + - bStats: [AuthMe on bstats.org](https://bstats.org/plugin/bukkit/AuthMe)


@@ -92,7 +86,7 @@ You can also create your own translation file and, if you want, you can share it
  • DoubleSaltedMD5: SALTED2MD5
  • WordPress: WORDPRESS
  • -
  • Custom MySQL tables/columns names (useful with forums databases)
  • +
  • Custom MySQL tables/columns names (useful with forum databases)
  • Cached database queries!
  • Fully compatible with Citizens2, CombatTag, CombatTagPlus!
  • Compatible with Minecraft mods like BuildCraft or RedstoneCraft
  • From 8aa573b9edc8e1348eecd6795605ee1a0575f8e5 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 10:58:29 +0100 Subject: [PATCH 59/79] Minor fixes as found by Checkstyle --- .codeclimate.yml | 2 ++ src/main/java/fr/xephi/authme/api/API.java | 1 + src/main/java/fr/xephi/authme/api/NewAPI.java | 1 + .../authme/command/CommandDescription.java | 2 +- .../executable/captcha/CaptchaCommand.java | 1 - .../fr/xephi/authme/datasource/Columns.java | 2 ++ .../converter/RakamakConverter.java | 25 +++++++++---------- .../authme/initialization/OnStartupTasks.java | 6 +++++ .../xephi/authme/listener/OnJoinVerifier.java | 2 +- .../protocollib/InventoryPacketAdapter.java | 3 ++- .../protocollib/TabCompletePacketAdapter.java | 2 +- .../handlers/PermissionsBukkitHandler.java | 11 +++++--- .../handlers/ZPermissionsHandler.java | 14 +++++++---- .../PasswordRegisterExecutorProvider.java | 17 ++++++++++--- .../security/crypts/SeparateSaltMethod.java | 6 ++--- .../security/crypts/UsernameSaltMethod.java | 8 +++--- .../xephi/authme/service/BukkitService.java | 3 +-- .../settings/properties/BackupSettings.java | 2 +- .../properties/ConverterSettings.java | 2 +- .../settings/properties/DatabaseSettings.java | 2 +- .../settings/properties/EmailSettings.java | 2 +- .../settings/properties/HooksSettings.java | 2 +- .../settings/properties/PluginSettings.java | 2 +- .../properties/ProtectionSettings.java | 2 +- .../settings/properties/PurgeSettings.java | 2 +- .../properties/RegistrationSettings.java | 5 ++-- .../properties/RestrictionSettings.java | 2 +- .../settings/properties/SecuritySettings.java | 5 ++-- .../xephi/authme/util/RandomStringUtils.java | 8 +++--- .../authme/util/lazytags/TagReplacer.java | 2 +- 30 files changed, 86 insertions(+), 58 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index ee496167..9c4405f8 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -25,4 +25,6 @@ exclude_paths: - 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java' - 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java' - 'src/main/java/fr/xephi/authme/security/crypts/BCryptService.java' +- 'src/main/java/fr/xephi/authme/security/crypts/PHPBB.java' - 'src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java' +- 'src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java' diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index d05fbe6b..75e746e6 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -23,6 +23,7 @@ import javax.inject.Inject; * @deprecated Use {@link NewAPI} */ @Deprecated +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore public class API { private static AuthMe instance; diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index d4f34b2c..2193f64c 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -24,6 +24,7 @@ import java.util.List; * NewAPI authmeApi = AuthMe.getApi(); * */ +@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore public class NewAPI { private static NewAPI singleton; diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 18463bca..57b92d73 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -19,7 +19,7 @@ import static java.util.Arrays.asList; * {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that * the child defines. */ -public class CommandDescription { +public final class CommandDescription { /** * Defines the labels to execute the command. For example, if labels are "register" and "r" and the parent is diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java index c5756cda..d7150c89 100644 --- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java @@ -3,7 +3,6 @@ package fr.xephi.authme.command.executable.captcha; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; diff --git a/src/main/java/fr/xephi/authme/datasource/Columns.java b/src/main/java/fr/xephi/authme/datasource/Columns.java index b6d732cd..f984007e 100644 --- a/src/main/java/fr/xephi/authme/datasource/Columns.java +++ b/src/main/java/fr/xephi/authme/datasource/Columns.java @@ -6,6 +6,8 @@ import fr.xephi.authme.settings.properties.DatabaseSettings; /** * Database column names. */ +// Justification: String is immutable and this class is used to easily access the configurable column names +@SuppressWarnings({"checkstyle:VisibilityModifier", "checkstyle:MemberName", "checkstyle:AbbreviationAsWordInName"}) public final class Columns { public final String NAME; diff --git a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java index eb9d904c..42a8ecf1 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; /** @@ -40,19 +41,17 @@ public class RakamakConverter implements Converter { @Override // TODO ljacqu 20151229: Restructure this into smaller portions public void execute(CommandSender sender) { - boolean useIP = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP); + boolean useIp = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP); String fileName = settings.getProperty(ConverterSettings.RAKAMAK_FILE_NAME); String ipFileName = settings.getProperty(ConverterSettings.RAKAMAK_IP_FILE_NAME); File source = new File(pluginFolder, fileName); - File ipfiles = new File(pluginFolder, ipFileName); - HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + File ipFiles = new File(pluginFolder, ipFileName); + Map playerIP = new HashMap<>(); + Map playerPassword = new HashMap<>(); try { - BufferedReader users; - BufferedReader ipFile; - ipFile = new BufferedReader(new FileReader(ipfiles)); + BufferedReader ipFile = new BufferedReader(new FileReader(ipFiles)); String line; - if (useIP) { + if (useIp) { String tempLine; while ((tempLine = ipFile.readLine()) != null) { if (tempLine.contains("=")) { @@ -63,20 +62,20 @@ public class RakamakConverter implements Converter { } ipFile.close(); - users = new BufferedReader(new FileReader(source)); + BufferedReader users = new BufferedReader(new FileReader(source)); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]); - playerPSW.put(arguments[0], hashedPassword); + playerPassword.put(arguments[0], hashedPassword); } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPassword.entrySet()) { String playerName = m.getKey(); - HashedPassword psw = playerPSW.get(playerName); - String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; + HashedPassword psw = playerPassword.get(playerName); + String ip = useIp ? playerIP.get(playerName) : "127.0.0.1"; PlayerAuth auth = PlayerAuth.builder() .name(playerName) .realName(playerName) diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 34996508..8767e88a 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -45,6 +45,12 @@ public class OnStartupTasks { OnStartupTasks() { } + /** + * Sends bstats metrics. + * + * @param plugin the plugin instance + * @param settings the settings + */ public static void sendMetrics(AuthMe plugin, Settings settings) { final Metrics metrics = new Metrics(plugin); diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index e3ff4ec5..06a8761a 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -111,7 +111,7 @@ class OnJoinVerifier implements Reloadable { * @param event the login event to verify * * @return true if the player's connection should be refused (i.e. the event does not need to be processed - * further), false if the player is not refused + * further), false if the player is not refused */ public boolean refusePlayerForFullServer(PlayerLoginEvent event) { final Player player = event.getPlayer(); diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java index 48cf1868..05150765 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java @@ -14,6 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package fr.xephi.authme.listener.protocollib; import com.comphenix.protocol.PacketType; @@ -46,7 +47,7 @@ class InventoryPacketAdapter extends PacketAdapter { private static final int MAIN_SIZE = 27; private static final int HOTBAR_SIZE = 9; - public InventoryPacketAdapter(AuthMe plugin) { + InventoryPacketAdapter(AuthMe plugin) { super(plugin, PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS); } diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java index 4476f80a..daf68638 100644 --- a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java +++ b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java @@ -12,7 +12,7 @@ import fr.xephi.authme.data.auth.PlayerCache; class TabCompletePacketAdapter extends PacketAdapter { - public TabCompletePacketAdapter(AuthMe plugin) { + TabCompletePacketAdapter(AuthMe plugin) { super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE); } diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java index b7773e7a..acae466c 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java @@ -31,7 +31,8 @@ public class PermissionsBukkitHandler implements PermissionHandler { @Override public boolean addToGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player addgroup " + player.getName() + " " + group); } @Override @@ -46,17 +47,19 @@ public class PermissionsBukkitHandler implements PermissionHandler { @Override public boolean removeFromGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player removegroup " + player.getName() + " " + group); } @Override public boolean setGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player setgroup " + player.getName() + " " + group); } @Override public List getGroups(Player player) { - List groups = new ArrayList(); + List groups = new ArrayList<>(); for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) { groups.add(group.getName()); } diff --git a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java index 1de19f1d..c86863f7 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java @@ -30,7 +30,8 @@ public class ZPermissionsHandler implements PermissionHandler { @Override public boolean addToGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " addgroup " + group); } @Override @@ -41,20 +42,23 @@ public class ZPermissionsHandler implements PermissionHandler { @Override public boolean hasPermissionOffline(String name, PermissionNode node) { Map perms = zPermissionsService.getPlayerPermissions(null, null, name); - if (perms.containsKey(node.getNode())) + if (perms.containsKey(node.getNode())) { return perms.get(node.getNode()); - else + } else { return false; + } } @Override public boolean removeFromGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " removegroup " + group); } @Override public boolean setGroup(Player player, String group) { - return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + group); + return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "permissions player " + player.getName() + " setgroup " + group); } @Override diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java index a79c63c8..9d40bcf8 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java @@ -56,10 +56,10 @@ class PasswordRegisterExecutorProvider { /** Registration executor for password registration. */ class PasswordRegisterExecutor implements RegistrationExecutor { - protected final Player player; + private final Player player; private final String password; private final String email; - protected HashedPassword hashedPassword; + private HashedPassword hashedPassword; /** * Constructor. @@ -105,6 +105,14 @@ class PasswordRegisterExecutorProvider { } syncProcessManager.processSyncPasswordRegister(player); } + + protected Player getPlayer() { + return player; + } + + protected HashedPassword getHashedPassword() { + return hashedPassword; + } } /** Executor for password registration via API call. */ @@ -147,8 +155,9 @@ class PasswordRegisterExecutorProvider { public void executePostPersistAction() { super.executePostPersistAction(); - String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash()); - commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl); + String hash = getHashedPassword().getHash(); + String qrCodeUrl = TwoFactor.getQRBarcodeURL(getPlayer().getName(), Bukkit.getIp(), hash); + commonService.send(getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java index 7d4b3d95..d0dacda4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -8,15 +8,15 @@ public abstract class SeparateSaltMethod implements EncryptionMethod { @Override public abstract String computeHash(String password, String salt, String name); - @Override - public abstract String generateSalt(); - @Override public HashedPassword computeHash(String password, String name) { String salt = generateSalt(); return new HashedPassword(computeHash(password, salt, name), salt); } + @Override + public abstract String generateSalt(); + @Override public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null)); diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java index 698979d8..23101e22 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -17,13 +17,13 @@ public abstract class UsernameSaltMethod implements EncryptionMethod { public abstract HashedPassword computeHash(String password, String name); @Override - public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { - return hashedPassword.getHash().equals(computeHash(password, name).getHash()); + public String computeHash(String password, String salt, String name) { + return computeHash(password, name).getHash(); } @Override - public String computeHash(String password, String salt, String name) { - return computeHash(password, name).getHash(); + public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { + return hashedPassword.getHash().equals(computeHash(password, name).getHash()); } @Override diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java index 14cf0dcb..e945cebd 100644 --- a/src/main/java/fr/xephi/authme/service/BukkitService.java +++ b/src/main/java/fr/xephi/authme/service/BukkitService.java @@ -13,7 +13,6 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.Event; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; @@ -172,7 +171,7 @@ public class BukkitService implements SettingsDependent { * @return a BukkitTask that contains the id number * @throws IllegalArgumentException if plugin is null * @throws IllegalStateException if this was already scheduled - * @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long) + * @see BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long) */ public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) { return task.runTaskTimer(authMe, delay, period); diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java index 162bf8aa..57bb5941 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class BackupSettings implements SettingsHolder { +public final class BackupSettings implements SettingsHolder { @Comment("Enable or disable automatic backup") public static final Property ENABLED = diff --git a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java index ae289e54..d2b34c9a 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class ConverterSettings implements SettingsHolder { +public final class ConverterSettings implements SettingsHolder { @Comment("Rakamak file name") public static final Property RAKAMAK_FILE_NAME = diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java index fde994af..378bd198 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -7,7 +7,7 @@ import fr.xephi.authme.datasource.DataSourceType; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class DatabaseSettings implements SettingsHolder { +public final class DatabaseSettings implements SettingsHolder { @Comment({"What type of database do you want to use?", "Valid values: sqlite, mysql"}) diff --git a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java index 3a9ede5d..f7522b94 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java @@ -9,7 +9,7 @@ import java.util.List; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class EmailSettings implements SettingsHolder { +public final class EmailSettings implements SettingsHolder { @Comment("Email SMTP server host") public static final Property SMTP_HOST = diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java index f512d67b..d4e80a1c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -9,7 +9,7 @@ import java.util.List; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class HooksSettings implements SettingsHolder { +public final class HooksSettings implements SettingsHolder { @Comment("Do we need to hook with multiverse for spawn checking?") public static final Property MULTIVERSE = 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 5f45ca5d..d99ffa0b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java @@ -7,7 +7,7 @@ import fr.xephi.authme.output.LogLevel; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class PluginSettings implements SettingsHolder { +public final class PluginSettings implements SettingsHolder { @Comment({ "Do you want to enable the session feature?", diff --git a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java index 3a19a70e..2bae7179 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class ProtectionSettings implements SettingsHolder { +public final class ProtectionSettings implements SettingsHolder { @Comment("Enable some servers protection (country based login, antibot)") public static final Property ENABLE_PROTECTION = diff --git a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java index 0cfa029a..2c62454c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java @@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class PurgeSettings implements SettingsHolder { +public final class PurgeSettings implements SettingsHolder { @Comment("If enabled, AuthMe automatically purges old, unused accounts") public static final Property USE_AUTO_PURGE = diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java index 38615b78..7d87e77b 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java @@ -8,7 +8,7 @@ import fr.xephi.authme.process.register.RegistrationType; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class RegistrationSettings implements SettingsHolder { +public final class RegistrationSettings implements SettingsHolder { @Comment("Enable registration on the server?") public static final Property IS_ENABLED = @@ -42,7 +42,8 @@ public class RegistrationSettings implements SettingsHolder { "EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address" }) public static final Property REGISTER_SECOND_ARGUMENT = - newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", RegisterSecondaryArgument.CONFIRMATION); + newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", + RegisterSecondaryArgument.CONFIRMATION); @Comment({ "Do we force kick a player after a successful registration?", diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index d0677579..5a007b32 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class RestrictionSettings implements SettingsHolder { +public final class RestrictionSettings implements SettingsHolder { @Comment({ "Can not authenticated players chat?", diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java index 496fb3b2..455af7e8 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -12,7 +12,7 @@ import java.util.Set; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty; -public class SecuritySettings implements SettingsHolder { +public final class SecuritySettings implements SettingsHolder { @Comment({"Stop the server if we can't contact the sql database", "Take care with this, if you set this to false,", @@ -86,7 +86,8 @@ public class SecuritySettings implements SettingsHolder { "- 'password'", "- 'help'"}) public static final Property> UNSAFE_PASSWORDS = - newLowercaseListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321", "123456789", "help"); + newLowercaseListProperty("settings.security.unsafePasswords", + "123456", "password", "qwerty", "12345", "54321", "123456789", "help"); @Comment("Tempban a user's IP address if they enter the wrong password too many times") public static final Property TEMPBAN_ON_MAX_LOGINS = diff --git a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java index db166a74..2dce3c64 100644 --- a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java +++ b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java @@ -24,7 +24,7 @@ public final class RandomStringUtils { * @return The random string */ public static String generate(int length) { - return generate(length, LOWER_ALPHANUMERIC_INDEX); + return generateString(length, LOWER_ALPHANUMERIC_INDEX); } /** @@ -35,7 +35,7 @@ public final class RandomStringUtils { * @return The random hexadecimal string */ public static String generateHex(int length) { - return generate(length, HEX_MAX_INDEX); + return generateString(length, HEX_MAX_INDEX); } /** @@ -46,10 +46,10 @@ public final class RandomStringUtils { * @return The random string */ public static String generateLowerUpper(int length) { - return generate(length, CHARS.length); + return generateString(length, CHARS.length); } - private static String generate(int length, int maxIndex) { + private static String generateString(int length, int maxIndex) { if (length < 0) { throw new IllegalArgumentException("Length must be positive but was " + length); } diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java index 660132fb..a9d19319 100644 --- a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java +++ b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java @@ -11,7 +11,7 @@ import java.util.stream.Collectors; * * @param the argument type */ -public class TagReplacer { +public final class TagReplacer { private final List> tags; private final Collection messages; From 6db778387d892237b4cce89a7f0fd98a4a612e1a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 11:08:43 +0100 Subject: [PATCH 60/79] Don't make CommandDescription final as to allow mocks - Construction of a CommandDescription requires a lot of fields to be set. In most tests we only care about one or two fields -> having to set a lot of fields to dummy values is not very nice. --- src/main/java/fr/xephi/authme/command/CommandDescription.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 57b92d73..e195c475 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -19,7 +19,8 @@ import static java.util.Arrays.asList; * {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that * the child defines. */ -public final class CommandDescription { +@SuppressWarnings("checkstyle:FinalClass") // Justification: class is mocked in multiple tests +public class CommandDescription { /** * Defines the labels to execute the command. For example, if labels are "register" and "r" and the parent is From 648e71cf0f3c1c8cf6f6087a7127caf2fdd50488 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 6 Mar 2017 06:11:59 +0200 Subject: [PATCH 61/79] IPB4 Improve IPB4 prefix, group, lastvisit support added. --- .../fr/xephi/authme/datasource/MySQL.java | 41 +++++++++++++++++-- .../settings/properties/HooksSettings.java | 8 ++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index f62b00e1..3c007282 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -45,7 +45,9 @@ public class MySQL implements DataSource { private HikariDataSource ds; private String phpBbPrefix; + private String IPBPrefix; private int phpBbGroup; + private int IPBGroup; private String wordpressPrefix; public MySQL(Settings settings) throws ClassNotFoundException, SQLException { @@ -96,6 +98,8 @@ public class MySQL implements DataSource { this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX); this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID); + this.IPBPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX); + this.IPBGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID); this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); if (poolSize == -1) { @@ -334,8 +338,39 @@ public class MySQL implements DataSource { pst.close(); } } - - if (hashAlgorithm == HashAlgorithm.PHPBB) { + if (hashAlgorithm == HashAlgorithm.IPB4){ + sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + pst = con.prepareStatement(sql); + pst.setString(1, auth.getNickname()); + rs = pst.executeQuery(); + if (rs.next()){ + // Update player group in core_members + sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, IPBGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Get current time without ms + long time = System.currentTimeMillis() / 1000; + // update joined date + sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".joined=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setLong(1, time); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Update last_visit + sql = "UPDATE " + IPBPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setLong(1, time); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + } + rs.close(); + pst.close(); + } else if (hashAlgorithm == HashAlgorithm.PHPBB) { sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; pst = con.prepareStatement(sql); pst.setString(1, auth.getNickname()); @@ -477,7 +512,7 @@ public class MySQL implements DataSource { pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;"); pst.setString(1, auth.getNickname()); rs = pst.executeQuery(); - if (rs.next()) { + if (rs.next()) { int id = rs.getInt(col.ID); sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)"; pst2 = con.prepareStatement(sql); diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java index f512d67b..e82aadfa 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -54,6 +54,14 @@ public class HooksSettings implements SettingsHolder { public static final Property PHPBB_ACTIVATED_GROUP_ID = newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2); + @Comment("IP Board table prefix defined during the IP Board installation process") + public static final Property IPB_TABLE_PREFIX = + newProperty("ExternalBoardOptions.IPBTablePrefix", "ipb_"); + + @Comment("IP Board default group ID; 3 is the default registered group defined by IP Board ") + public static final Property IPB_ACTIVATED_GROUP_ID = + newProperty("ExternalBoardOptions.IPBActivatedGroupId", 3); + @Comment("Wordpress prefix defined during WordPress installation") public static final Property WORDPRESS_TABLE_PREFIX = newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_"); From 6d67b828606d88fd409f3ba1becc71065217c6a0 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 6 Mar 2017 06:31:51 +0200 Subject: [PATCH 62/79] Xenforo Added Xenforo group --- src/main/java/fr/xephi/authme/datasource/MySQL.java | 9 +++++++++ .../xephi/authme/settings/properties/HooksSettings.java | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 3c007282..9dc84253 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -48,6 +48,7 @@ public class MySQL implements DataSource { private String IPBPrefix; private int phpBbGroup; private int IPBGroup; + private int XFGroup; private String wordpressPrefix; public MySQL(Settings settings) throws ClassNotFoundException, SQLException { @@ -100,6 +101,7 @@ public class MySQL implements DataSource { this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID); this.IPBPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX); this.IPBGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID); + this.XFGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID); this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); if (poolSize == -1) { @@ -525,6 +527,13 @@ public class MySQL implements DataSource { pst2.setBlob(3, blob); pst2.executeUpdate(); pst2.close(); + // Update player group in core_members + sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, XFGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); } rs.close(); pst.close(); diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java index 05e2e3d9..e752057c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java @@ -58,9 +58,13 @@ public final class HooksSettings implements SettingsHolder { public static final Property IPB_TABLE_PREFIX = newProperty("ExternalBoardOptions.IPBTablePrefix", "ipb_"); - @Comment("IP Board default group ID; 3 is the default registered group defined by IP Board ") + @Comment("IP Board default group ID; 3 is the default registered group defined by IP Board") public static final Property IPB_ACTIVATED_GROUP_ID = newProperty("ExternalBoardOptions.IPBActivatedGroupId", 3); + + @Comment("XenForo default group ID; 2 is the default registered group defined by Xenforo") + public static final Property XF_ACTIVATED_GROUP_ID = + newProperty("ExternalBoardOptions.XFActivatedGroupId", 2); @Comment("Wordpress prefix defined during WordPress installation") public static final Property WORDPRESS_TABLE_PREFIX = From 5ca1c17771571c338c6f806e00a3e031736b609e Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 8 Mar 2017 01:21:29 +0200 Subject: [PATCH 63/79] Xenforo support Added in-game xenforo support. User can register/login in-game, in website only login. Register have issues i will try to fix it. --- .../fr/xephi/authme/datasource/MySQL.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 9dc84253..2ca1df55 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -516,6 +516,7 @@ public class MySQL implements DataSource { rs = pst.executeQuery(); if (rs.next()) { int id = rs.getInt(col.ID); + // Insert player password, salt in xf_user_authenticate sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)"; pst2 = con.prepareStatement(sql); pst2.setInt(1, id); @@ -527,13 +528,39 @@ public class MySQL implements DataSource { pst2.setBlob(3, blob); pst2.executeUpdate(); pst2.close(); - // Update player group in core_members + // Update player group in xf_users sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;"; pst2 = con.prepareStatement(sql); pst2.setInt(1, XFGroup); pst2.setString(2, auth.getNickname()); pst2.executeUpdate(); pst2.close(); + // Update player permission combination in xf_users + sql = "UPDATE " + tableName + " SET "+ tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, XFGroup); + pst2.setString(2, auth.getNickname()); + pst2.executeUpdate(); + pst2.close(); + // Insert player privacy combination in xf_user_privacy + sql = "INSERT INTO xf_user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, id); + pst2.setString(2, "everyone"); + pst2.setString(3, "members"); + pst2.setString(4, "members"); + pst2.setString(5, "everyone"); + pst2.setString(6, "everyone"); + pst2.executeUpdate(); + pst2.close(); + // Insert player group relation in xf_user_group_relation + sql = "INSERT INTO xf_user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)"; + pst2 = con.prepareStatement(sql); + pst2.setInt(1, id); + pst2.setInt(2, XFGroup); + pst2.setString(3, "1"); + pst2.executeUpdate(); + pst2.close(); } rs.close(); pst.close(); From bdd62e70c22e882f95bc00e95dbcfee08ab595a7 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 9 Mar 2017 00:09:37 +0200 Subject: [PATCH 64/79] Update BG language (#226) * Update language Updated Bulgarian language. * Update messages_bg.yml * Update messages_bg.yml * Update messages_bg.yml * Update messages_bg.yml Ready. :) * Update messages_bg.yml --- src/main/resources/messages/messages_bg.yml | 155 ++++++++++---------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 38e79fac..ab3bdeac 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -1,105 +1,104 @@ # Registration -reg_msg: '&cМоля регистрирай се с "/register <парола> <парола>"' +reg_msg: '&3Моля регистрирайте се с: /register парола парола' usage_reg: '&cКоманда: /register парола парола' -reg_only: '&fСамо за регистрирани! Моля посети http://example.com за регистрация' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' -registered: '&cУспешно премахната регистрация!' +reg_only: '&4Само регистрирани потребители могат да влизат в сървъра! Моля посетете http://example.com, за да се регистрирате!' +kicked_admin_registered: 'Ти беше регистриран от администратора, моля влезте отново' +registered: '&2Успешна регистрация!' reg_disabled: '&cРегистрациите са изключени!' -user_regged: '&cПотребителското име е заето!' +user_regged: '&cПотребителското име е заетo!' # Password errors on registration -password_error: '&fПаролата не съвпада' -# TODO password_error_nick: '&cYou can''t use your name as password, please choose another one...' -# TODO password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' -# TODO password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' -pass_len: '&cВашета парола не е достатъчно дълга или къса.' +password_error: '&cПаролите не съвпадат, провете ги отново!' +password_error_nick: '&cНе можеш да използваш потребителското си име за парола, моля изберете друга парола.' +password_error_unsafe: '&cИзбраната парола не е безопасна, моля изберете друга парола.' +password_error_chars: '&4Паролата съдържа непозволени символи. Позволени символи: REG_EX' +pass_len: '&cПаролата е твърде къса или прекалено дълга! Моля опитайте с друга парола.' # Login usage_log: '&cКоманда: /login парола' wrong_pwd: '&cГрешна парола!' -login: '&cВход успешен!' -login_msg: '&cМоля влез с "/login парола"' -timeout: '&fВремето изтече, опитай отново!' +login: '&2Успешен вход!' +login_msg: '&cМоля влезте с: /login парола' +timeout: '&4Времето за вход изтече, беше кикнат от сървъра. Моля опитайте отново!' # Errors -unknown_user: '&cПотребителя не е регистриран' -# TODO denied_command: '&cIn order to use this command you must be authenticated!' -# TODO denied_chat: '&cIn order to chat you must be authenticated!' +unknown_user: '&cПотребителското име не е регистрирано!' +denied_command: '&cЗа да използваш тази команда трябва да си си влезнал в акаунта!' +denied_chat: '&cЗа да пишеш в чата трябва даи сиси влезнал в акаунта!' not_logged_in: '&cНе си влязъл!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' -# TODO: Missing tags %reg_count, %max_acc, %reg_names -max_reg: '&fТи достигна максималния брой регистрации!' -no_perm: '&cНямаш Достъп!' -error: '&fПолучи се грешка; Моля свържете се с админ' -unsafe_spawn: '&fТвоята локация когато излезе не беше безопасна, телепортиран си на Spawn!' -kick_forvip: '&cVIP влезе докато сървъра е пълен, ти беше изгонен!' +tempban_max_logins: '&cТи беше баннат временно, понеже си сгрешил паролата прекалено много пъти.' +max_reg: '&cТи си достигнал максималният брой регистрации (%reg_count/%max_acc %reg_names)!' +no_perm: '&4Нямаш нужните права за това действие!' +error: '&4Получи се неочаквана грешка, моля свържете се с администратора!' +unsafe_spawn: '&cКогато излезе твоето местоположение не беше безопастно, телепортиран си на Spawn.' +kick_forvip: '&3VIP потребител влезе докато сървъра беше пълен, ти беше изгонен!' # AntiBot -# TODO kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.' -antibot_auto_enabled: '[AuthMe] AntiBotMod автоматично включен, открита е потенциална атака!' -antibot_auto_disabled: '[AuthMe] AntiBotMod автоматично изключване след %m Минути.' +kick_antibot: 'Защитата от ботове е включена! Трябва да изчакаш няколко минути преди да влезеш в сървъра.' +antibot_auto_enabled: '&4Защитата за ботове е включена заради потенциална атака!' +antibot_auto_disabled: '&2Защитата за ботове ще се изключи след %m минута/и!' # Other messages -unregistered: '&cУспешно от-регистриран!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' -# TODO two_factor_create: '&2Your secret code is %code. You can scan it from here %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' -vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' +unregistered: '&cРегистрацията е премахната успешно!' +accounts_owned_self: 'Претежаваш %count акаунт/а:' +accounts_owned_other: 'Потребителят %name има %count акаунт/а:' +TODO two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url' +recovery_code_sent: 'Възстановяващият код беше изпратен на твоят email адрес.' +recovery_code_incorrect: 'Възстановяващият код е неправилен! Използвайте: /email recovery имейл, за да генерирате нов' +vb_nonActiv: '&cТвоят акаунт все още не е актириван, моля провете своят email адрес!' usage_unreg: '&cКоманда: /unregister парола' -pwd_changed: '&cПаролата е променена!' -logged_in: '&cВече сте влязъл!' -logout: '&cУспешен изход от регистрацията!' -reload: '&fКонфигурацията презаредена!' -usage_changepassword: '&fКоманда: /changepassword СтараПарола НоваПарола' +pwd_changed: '&2Паротала е променена успешно!' +logged_in: '&cВече си вписан!' +logout: '&2Излязохте успешно!' +reload: '&2Конфигурацията и база данните бяха презаредени правилно!' +usage_changepassword: '&cКоманда: /changepassword Стара-Парола Нова-Парола' # Session messages -# TODO invalid_session: '&cYour IP has been changed and your session data has expired!' -valid_session: '&aСесията продължена!' +invalid_session: '&cТвоят IP се е променил и сесията беше прекратена.' +valid_session: '&2Сесията е продължена.' # Error messages when joining -name_len: '&cТвоя никнейм е твърде малък или голям' -regex: '&cТвоя никнейм съдържа забранени знацхи. Позволените са: REG_EX' -country_banned: 'Твоята държава е забранена в този сървър!' -# TODO not_owner_error: 'You are not the owner of this account. Please choose another name!' -kick_fullserver: '&cСървъра е пълен, Съжеляваме!' -same_nick: '&fПотребител с този никнейм е в игра' -# TODO invalid_name_case: 'You should join using username %valid, not %invalid.' -# TODO same_ip_online: 'A player with the same IP is already in game!' +name_len: '&4Потребителското име е прекалено късо или дълга. Моля опитайте с друго потребителско име!' +regex: '&4Потребителското име съдържа забранени знаци. Позволени знаци: REG_EX' +country_banned: '&4Твоята държава е забранена в този сървър!' +not_owner_error: 'Ти не си собственика на този акаунт. Моля избери друго потребителско име!' +kick_fullserver: '&4Сървъра е пълен, моля опитайте отново!' +same_nick: '&4Вече има потребител, който играете в сървъра със същото потребителско име!' +invalid_name_case: 'Трябва да влезеш с %valid, а не с %invalid.' +same_ip_online: 'Вече има потребител със същото IP в сървъра!' # Email -usage_email_add: '&fКоманда: /email add ' -usage_email_change: '&fКоманда: /email change <СтарИмейл> <НовИмейл> ' -usage_email_recovery: '&fКоманда: /email recovery <имейл>' -new_email_invalid: '[AuthMe] Новия имейл е грешен!' -old_email_invalid: '[AuthMe] Стария имейл е грешен!' -email_invalid: '[AuthMe] Грешен имейл' -email_added: '[AuthMe] Имейла добавен !' -email_confirm: '[AuthMe] Потвърди своя имейл !' -email_changed: '[AuthMe] Имейла е сменен !' -email_send: '[AuthMe] Изпраен е имейл !' -# TODO email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' -# TODO email_already_used: '&4The email address is already being used' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' -add_email: '&cМоля добави своя имейл с : /email add имейл имейл' -recovery_email: '&cЗабравихте своята парола? Моля използвай /email recovery <имейл>' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +usage_email_add: '&cКоманда: /email add имейл имейл' +usage_email_change: '&cКоманда: /email change Стар-Имейл Нов-Имейл' +usage_email_recovery: '&cКоманда: /email recovery имейл' +new_email_invalid: '&cНовият имейл е грешен, опитайте отново!' +old_email_invalid: '&cСтарият имейл е грешен, опитайте отново!' +email_invalid: '&cИмейла е невалиден, опитайте с друг!' +email_added: '&2Имейл адреса е добавен!' +email_confirm: '&cМоля потвърди своя имейл адрес!' +email_changed: '&2Имейл адреса е сменен!' +email_send: '&2Възстановяващият имейл е изпратен успешно. Моля провете пощата си!' +email_exists: '&cВъзстановяващият имейл е бил изпратен. Може да го откажеш или изпратиш отново с тази команда:' +email_show: '&2Твоят имейл адрес е: &f%email' +incomplete_email_settings: 'Грешка: Не всички настройки са написани за изпращане на имейл адрес. Моля свържете се с администратора!' +email_already_used: '&4Имейл адреса вече се използва, опитайте с друг.' +email_send_failure: 'Съобщението не беше изпратено. Моля свържете се с администратора.' +show_no_email: '&2Няма добавен имейл адрес към акаунта.' +add_email: '&3Моля добавете имейл адрес към своят акаунт: /email add имейл имейл' +recovery_email: '&3Забравена парола? Използвайте: /email recovery имейл' +email_cooldown_error: '&cВече е бил изпратен имейл адрес. Трябва а изчакаш %time преди да пратиш нов.' # Captcha -usage_captcha: '&cYou need to type a captcha, please type: /captcha ' -wrong_captcha: '&cГрешен код, използвай : /captcha THE_CAPTCHA' -valid_captcha: '&cТвоя код е валиден!' +usage_captcha: '&3Моля въведе цифрите/буквите от капчата: /captcha ' +wrong_captcha: '&cКода е грешен, използвайте: "/captcha THE_CAPTCHA" в чата!' +valid_captcha: '&2Кода е валиден!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'секунда' +seconds: 'секунди' +minute: 'минута' +minutes: 'минути' +hour: 'час' +hours: 'часа' +day: 'ден' +days: 'дена' From e43f6364ed1e32ab935a1ab253671d55a769e39f Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 9 Mar 2017 07:56:36 +0100 Subject: [PATCH 65/79] Remove forgotten TODO in messages_bg.yml --- src/main/resources/messages/messages_bg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index ab3bdeac..a39a9d53 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -42,7 +42,7 @@ antibot_auto_disabled: '&2Защитата за ботове ще се изкл unregistered: '&cРегистрацията е премахната успешно!' accounts_owned_self: 'Претежаваш %count акаунт/а:' accounts_owned_other: 'Потребителят %name има %count акаунт/а:' -TODO two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url' +two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url' recovery_code_sent: 'Възстановяващият код беше изпратен на твоят email адрес.' recovery_code_incorrect: 'Възстановяващият код е неправилен! Използвайте: /email recovery имейл, за да генерирате нов' vb_nonActiv: '&cТвоят акаунт все още не е актириван, моля провете своят email адрес!' From 3b70492bb9e3ec80cd470eea1d9ffd92cfa7cb0e Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 9 Mar 2017 08:02:37 +0100 Subject: [PATCH 66/79] #1131 Add debug statement for country protection --- .../java/fr/xephi/authme/service/ValidationService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index 7c7abf78..691e3861 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -1,6 +1,7 @@ package fr.xephi.authme.service; import ch.jalu.configme.properties.Property; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.message.MessageKey; @@ -115,9 +116,10 @@ public class ValidationService implements Reloadable { } String countryCode = geoIpService.getCountryCode(hostAddress); - return validateWhitelistAndBlacklist(countryCode, - ProtectionSettings.COUNTRIES_WHITELIST, - ProtectionSettings.COUNTRIES_BLACKLIST); + boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode, + ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_BLACKLIST); + ConsoleLogger.debug("Country code '{0}' for '{1}' is allowed: {2}", countryCode, hostAddress, isCountryAllowed); + return isCountryAllowed; } /** @@ -194,6 +196,7 @@ public class ValidationService implements Reloadable { public MessageKey getMessageKey() { return messageKey; } + public String[] getArgs() { return args; } From ed55c77706cad176cbccc3b9c1b6a2995d9d0f0c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 9 Mar 2017 08:04:47 +0100 Subject: [PATCH 67/79] #1131 Correct quote type in debug statement - Java util Logger does not escape placeholders if they are in normal single quotes --- src/main/java/fr/xephi/authme/service/ValidationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index 691e3861..dd1f6e51 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -118,7 +118,7 @@ public class ValidationService implements Reloadable { String countryCode = geoIpService.getCountryCode(hostAddress); boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode, ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_BLACKLIST); - ConsoleLogger.debug("Country code '{0}' for '{1}' is allowed: {2}", countryCode, hostAddress, isCountryAllowed); + ConsoleLogger.debug("Country code `{0}` for `{1}` is allowed: {2}", countryCode, hostAddress, isCountryAllowed); return isCountryAllowed; } From f7de74c1349feeaf0c3eb39a2ce582888590ee47 Mon Sep 17 00:00:00 2001 From: RatchetCinemaESP Date: Thu, 9 Mar 2017 13:33:16 +0100 Subject: [PATCH 68/79] Translate messages_es.yml (#229) Affected lines: #92 and from #100 to #107 Regards! --- src/main/resources/messages/messages_es.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index ac741571..5ee7a564 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -89,7 +89,7 @@ email_send_failure: 'No se ha podido enviar el correo electrónico. Por favor, c show_no_email: '&2No tienes ningun E-Mail asociado en esta cuenta.' add_email: '&cPor favor agrega tu e-mail con: /email add tuEmail confirmarEmail' recovery_email: '&c¿Olvidaste tu contraseña? Por favor usa /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cEl correo ha sido enviado recientemente. Debes esperar %time antes de volver a enviar uno nuevo.' # Captcha usage_captcha: '&cUso: /captcha ' @@ -97,11 +97,11 @@ wrong_captcha: '&cCaptcha incorrecto, por favor usa: /captcha THE_CAPTCHA' valid_captcha: '&c¡ Captcha ingresado correctamente !' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'día' +days: 'días' From e788da87d427cfe747ea05ce5657cec1c5850ca7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 9 Mar 2017 22:25:52 +0100 Subject: [PATCH 69/79] #1034 #1131 Create debug section for country info / restrictions --- .../authme/debug/CountryLookup.java | 77 +++++++++++++++++++ .../executable/authme/debug/DebugCommand.java | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java new file mode 100644 index 00000000..05de8ab1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java @@ -0,0 +1,77 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.service.GeoIpService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.settings.properties.ProtectionSettings; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Shows the GeoIP information as returned by the geoIpService. + */ +class CountryLookup implements DebugSection { + + private static final Pattern IS_IP_ADDR = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}"); + + @Inject + private GeoIpService geoIpService; + + @Inject + private DataSource dataSource; + + @Inject + private ValidationService validationService; + + @Override + public String getName() { + return "cty"; + } + + @Override + public String getDescription() { + return "Check country protection / country data"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("Check player: /authme debug cty Bobby"); + sender.sendMessage("Check IP address: /authme debug cty 127.123.45.67"); + return; + } + + String argument = arguments.get(0); + if (IS_IP_ADDR.matcher(argument).matches()) { + outputInfoForIpAddr(sender, argument); + } else { + outputInfoForPlayer(sender, argument); + } + } + + private void outputInfoForIpAddr(CommandSender sender, String ipAddr) { + sender.sendMessage("IP '" + ipAddr + "' maps to country '" + geoIpService.getCountryCode(ipAddr) + + "' (" + geoIpService.getCountryName(ipAddr) + ")"); + if (validationService.isCountryAdmitted(ipAddr)) { + sender.sendMessage(ChatColor.DARK_GREEN + "This IP address' country is not blocked"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "This IP address' country is blocked from the server"); + } + sender.sendMessage("Note: if " + ProtectionSettings.ENABLE_PROTECTION + " is false no country is blocked"); + } + + private void outputInfoForPlayer(CommandSender sender, String name) { + PlayerAuth auth = dataSource.getAuth(name); + if (auth == null) { + sender.sendMessage("No player with name '" + name + "'"); + } else { + sender.sendMessage("Player '" + name + "' has IP address " + auth.getIp()); + outputInfoForIpAddr(sender, auth.getIp()); + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index c910cb92..510ac9a1 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -20,7 +20,7 @@ public class DebugCommand implements ExecutableCommand { private Factory debugSectionFactory; private Set> sectionClasses = - ImmutableSet.of(PermissionGroups.class, TestEmailSender.class); + ImmutableSet.of(PermissionGroups.class, TestEmailSender.class, CountryLookup.class); private Map sections; From 72acf2823385114ab873bb58c2ca8a6b8f128149 Mon Sep 17 00:00:00 2001 From: rafael59r2 Date: Sun, 12 Mar 2017 06:14:02 +0000 Subject: [PATCH 70/79] Creating help_pt.yml (#231) * Create help_pt.yml * Update help_pt.yml --- src/main/resources/messages/help_pt.yml | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/resources/messages/help_pt.yml diff --git a/src/main/resources/messages/help_pt.yml b/src/main/resources/messages/help_pt.yml new file mode 100644 index 00000000..2b2ead6c --- /dev/null +++ b/src/main/resources/messages/help_pt.yml @@ -0,0 +1,45 @@ +# Translation config for the AuthMe help, e.g. when /authme help or /authme help register is called + +# ------------------------------------------------------- +# List of texts used in the help section +common: + header: '==========[ AJUDA DO AuthMeReloaded ]==========' + optional: 'Opcional' + hasPermission: 'Tu tens permissão' + noPermission: 'Não tens permissão' + default: 'Padrão' + result: 'Resultado' + defaultPermissions: + notAllowed: 'Sem permissão' + opOnly: 'Só OP' + allowed: 'Toda gente é permitida' + +# ------------------------------------------------------- +# Titles of the individual help sections +# Set the translation text to empty text to disable the section, e.g. to hide alternatives: +# alternatives: '' +section: + command: 'Comando' + description: 'Breve descrição' + detailedDescription: 'Descrição detalhada' + arguments: 'Argumentos' + permissions: 'Permissões' + alternatives: 'Alternativas' + children: 'Comandos' + +# ------------------------------------------------------- +# You can translate the data for all commands using the below pattern. +# For example to translate /authme reload, create a section "authme.reload", or "login" for /login +# If the command has arguments, you can use arg1 as below to translate the first argument, and so forth +# Translations don't need to be complete; any missing section will be taken from the default silently +# Important: Put main commands like "authme" before their children (e.g. "authme.reload") +commands: + authme.register: + description: 'Registar um jogador' + detailedDescription: 'Registar um jogador com uma senha especifica.' + arg1: + label: 'jogador' + description: 'Nome de jogador' + arg2: + label: 'senha' + description: 'Senha' From 10d8f00c9277770a5e1ada40a92ae53226aeb984 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 12 Mar 2017 14:04:39 +0100 Subject: [PATCH 71/79] Various minor changes - AsynchronousLogin: call common permission methods through CommonService instead of PermissionsManager - CommandManager: remove superfluous replacement of %p (handled by lazy tag replacer) - Remove unused method in CommonService - Create DebugSectionConsistencyTest - SendMailSSL: Enable debug output if AuthMe log level is set to debug - Add Utils#logAndSendMessage and replace existing, separate implementations --- .../executable/authme/ReloadCommand.java | 17 +++---- .../fr/xephi/authme/mail/SendMailSSL.java | 5 ++ .../process/login/AsynchronousLogin.java | 10 ++-- .../xephi/authme/service/CommonService.java | 11 +--- .../commandconfig/CommandManager.java | 2 +- .../xephi/authme/task/purge/PurgeService.java | 11 +--- src/main/java/fr/xephi/authme/util/Utils.java | 18 +++++++ .../debug/DebugSectionConsistencyTest.java | 51 +++++++++++++++++++ .../fr/xephi/authme/mail/SendMailSSLTest.java | 4 ++ .../process/login/AsynchronousLoginTest.java | 17 +++---- .../authme/service/CommonServiceTest.java | 15 ------ .../authme/service/PluginHookServiceTest.java | 2 +- .../java/fr/xephi/authme/util/UtilsTest.java | 50 ++++++++++++++++++ 13 files changed, 148 insertions(+), 65 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java index 037846aa..5a3604f1 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java @@ -11,10 +11,10 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import javax.inject.Inject; -import java.util.Collection; import java.util.List; /** @@ -44,8 +44,7 @@ public class ReloadCommand implements ExecutableCommand { ConsoleLogger.setLoggingOptions(settings); // We do not change database type for consistency issues, but we'll output a note in the logs if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) { - ConsoleLogger.info("Note: cannot change database type during /authme reload"); - sender.sendMessage("Note: cannot change database type during /authme reload"); + Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload"); } performReloadOnServices(); commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); @@ -57,14 +56,10 @@ public class ReloadCommand implements ExecutableCommand { } private void performReloadOnServices() { - Collection reloadables = injector.retrieveAllOfType(Reloadable.class); - for (Reloadable reloadable : reloadables) { - reloadable.reload(); - } + injector.retrieveAllOfType(Reloadable.class) + .forEach(r -> r.reload()); - Collection settingsDependents = injector.retrieveAllOfType(SettingsDependent.class); - for (SettingsDependent dependent : settingsDependents) { - dependent.reload(settings); - } + injector.retrieveAllOfType(SettingsDependent.class) + .forEach(s -> s.reload(settings)); } } diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java index 344c39c9..ece40900 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSSL.java @@ -1,8 +1,10 @@ package fr.xephi.authme.mail; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.util.StringUtils; import org.apache.commons.mail.EmailConstants; import org.apache.commons.mail.EmailException; @@ -56,6 +58,9 @@ public class SendMailSSL { email.setFrom(senderMail, senderName); email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT)); email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword); + if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) { + email.setDebug(true); + } setPropertiesForPort(email, port); return email; diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 6fb8ff85..255b36b0 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -12,7 +12,6 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AdminPermission; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.AsynchronousProcess; @@ -46,9 +45,6 @@ public class AsynchronousLogin implements AsynchronousProcess { @Inject private CommonService service; - @Inject - private PermissionsManager permissionsManager; - @Inject private PlayerCache playerCache; @@ -300,10 +296,10 @@ public class AsynchronousLogin implements AsynchronousProcess { for (Player onlinePlayer : bukkitService.getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(player.getName()) - && permissionsManager.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) { + && service.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) { service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_SELF, Integer.toString(auths.size())); onlinePlayer.sendMessage(message); - } else if (permissionsManager.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) { + } else if (service.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) { service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER, player.getName(), Integer.toString(auths.size())); onlinePlayer.sendMessage(message); @@ -323,7 +319,7 @@ public class AsynchronousLogin implements AsynchronousProcess { boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) { // Do not perform the check if player has multiple accounts permission or if IP is localhost if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0 - || permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) + || service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) || "127.0.0.1".equalsIgnoreCase(ip) || "localhost".equalsIgnoreCase(ip)) { return false; diff --git a/src/main/java/fr/xephi/authme/service/CommonService.java b/src/main/java/fr/xephi/authme/service/CommonService.java index cea51ea0..d2381a45 100644 --- a/src/main/java/fr/xephi/authme/service/CommonService.java +++ b/src/main/java/fr/xephi/authme/service/CommonService.java @@ -65,16 +65,6 @@ public class CommonService { messages.send(sender, key, replacements); } - /** - * Retrieves a message. - * - * @param key the key of the message - * @return the message, split by line - */ - public String[] retrieveMessage(MessageKey key) { - return messages.retrieve(key); - } - /** * Retrieves a message in one piece. * @@ -102,6 +92,7 @@ public class CommonService { * @param player the player to process * @param group the group to add the player to */ + // TODO ljacqu 20170304: Move this out of CommonService public void setGroup(Player player, AuthGroupType group) { authGroupHandler.setGroup(player, group); } diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java index d87ebd5e..f996640c 100644 --- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java +++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java @@ -74,7 +74,7 @@ public class CommandManager implements Reloadable { private void executeCommands(Player player, List commands) { for (Command command : commands) { - final String execution = command.getCommand().replace("%p", player.getName()); + final String execution = command.getCommand(); if (Executor.CONSOLE.equals(command.getExecutor())) { bukkitService.dispatchConsoleCommand(execution); } else { diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java index fed07768..fe6d2fbf 100644 --- a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java +++ b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java @@ -9,14 +9,13 @@ import fr.xephi.authme.settings.properties.PurgeSettings; import fr.xephi.authme.util.Utils; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; import javax.inject.Inject; import java.util.Calendar; import java.util.Collection; import java.util.Set; -// TODO: move into services. -sgdc3 +import static fr.xephi.authme.util.Utils.logAndSendMessage; /** * Initiates purge tasks. @@ -119,12 +118,4 @@ public class PurgeService { void executePurge(Collection players, Collection names) { purgeExecutor.executePurge(players, names); } - - private static void logAndSendMessage(CommandSender sender, String message) { - ConsoleLogger.info(message); - // Make sure sender is not console user, which will see the message from ConsoleLogger already - if (sender != null && !(sender instanceof ConsoleCommandSender)) { - sender.sendMessage(message); - } - } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index b7628147..4d6983df 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -1,6 +1,8 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import java.util.Collection; import java.util.regex.Pattern; @@ -51,6 +53,22 @@ public final class Utils { } } + /** + * Sends a message to the given sender (null safe), and logs the message to the console. + * This method is aware that the command sender might be the console sender and avoids + * displaying the message twice in this case. + * + * @param sender the sender to inform + * @param message the message to log and send + */ + public static void logAndSendMessage(CommandSender sender, String message) { + ConsoleLogger.info(message); + // Make sure sender is not console user, which will see the message from ConsoleLogger already + if (sender != null && !(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(message); + } + } + /** * Null-safe way to check whether a collection is empty or not. * diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java new file mode 100644 index 00000000..18ab9fd2 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionConsistencyTest.java @@ -0,0 +1,51 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ClassCollector; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Consistency tests for {@link DebugSection} implementors. + */ +public class DebugSectionConsistencyTest { + + private static List> debugClasses; + + @BeforeClass + public static void collectClasses() { + debugClasses = new ClassCollector("src/main/java", "fr/xephi/authme/command/executable/authme/debug") + .collectClasses(); + } + + @Test + public void shouldAllBePackagePrivate() { + for (Class clazz : debugClasses) { + if (clazz != DebugCommand.class) { + assertThat(clazz + " should be package-private", + Modifier.isPublic(clazz.getModifiers()), equalTo(false)); + } + } + } + + @Test + public void shouldHaveDifferentSubcommandName() throws IllegalAccessException, InstantiationException { + Set names = new HashSet<>(); + for (Class clazz : debugClasses) { + if (DebugSection.class.isAssignableFrom(clazz) && !clazz.isInterface()) { + DebugSection debugSection = (DebugSection) clazz.newInstance(); + if (!names.add(debugSection.getName())) { + fail("Encountered name '" + debugSection.getName() + "' a second time in " + clazz); + } + } + } + } +} diff --git a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java index 835d4b49..74318498 100644 --- a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java +++ b/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java @@ -4,8 +4,10 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.settings.properties.PluginSettings; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; import org.junit.BeforeClass; @@ -49,6 +51,7 @@ public class SendMailSSLTest { public void initFields() throws IOException { given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn("mail@example.org"); given(settings.getProperty(EmailSettings.MAIL_PASSWORD)).willReturn("pass1234"); + given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.INFO); } @Test @@ -67,6 +70,7 @@ public class SendMailSSLTest { given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderAccount); String senderName = "Server administration"; given(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)).willReturn(senderName); + given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.DEBUG); // when HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); diff --git a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java index 12d4fee7..01b5e81c 100644 --- a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java +++ b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java @@ -6,10 +6,9 @@ import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -61,8 +60,6 @@ public class AsynchronousLoginTest { private LimboPlayerTaskManager limboPlayerTaskManager; @Mock private BukkitService bukkitService; - @Mock - private PermissionsManager permissionsManager; @BeforeClass public static void initLogger() { @@ -182,7 +179,7 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Carl"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(2); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); mockOnlinePlayersInBukkitService(); // when @@ -190,7 +187,7 @@ public class AsynchronousLoginTest { // then assertThat(result, equalTo(false)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verify(bukkitService).getOnlinePlayers(); } @@ -213,14 +210,14 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Frank"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(1); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); // when boolean result = asynchronousLogin.hasReachedMaxLoggedInPlayersForIp(player, "127.0.0.4"); // then assertThat(result, equalTo(false)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verifyZeroInteractions(bukkitService); } @@ -229,7 +226,7 @@ public class AsynchronousLoginTest { // given Player player = mockPlayer("Ian"); given(commonService.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP)).willReturn(2); - given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); + given(commonService.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(false); mockOnlinePlayersInBukkitService(); // when @@ -237,7 +234,7 @@ public class AsynchronousLoginTest { // then assertThat(result, equalTo(true)); - verify(permissionsManager).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); + verify(commonService).hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS); verify(bukkitService).getOnlinePlayers(); } diff --git a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java index 7927f826..ad0b7e73 100644 --- a/src/test/java/fr/xephi/authme/service/CommonServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/CommonServiceTest.java @@ -84,21 +84,6 @@ public class CommonServiceTest { verify(messages).send(sender, key, replacements); } - @Test - public void shouldRetrieveMessage() { - // given - MessageKey key = MessageKey.ACCOUNT_NOT_ACTIVATED; - String[] lines = new String[]{"First message line", "second line"}; - given(messages.retrieve(key)).willReturn(lines); - - // when - String[] result = commonService.retrieveMessage(key); - - // then - assertThat(result, equalTo(lines)); - verify(messages).retrieve(key); - } - @Test public void shouldRetrieveSingleMessage() { // given diff --git a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java index d4e3f8cd..e07ea1c2 100644 --- a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java @@ -58,7 +58,7 @@ public class PluginHookServiceTest { assertThat(pluginHookService.isEssentialsAvailable(), equalTo(true)); } - // Note ljacqu 20160312: Cannot test with Multiverse or CombatTagPlus because their classes are declared final + // Note ljacqu 20160312: Cannot test with CombatTagPlus because its class is declared final @Test public void shouldHookIntoEssentialsAtInitialization() { diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java index 458023d6..cbb64f37 100644 --- a/src/test/java/fr/xephi/authme/util/UtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -1,13 +1,20 @@ package fr.xephi.authme.util; import fr.xephi.authme.TestHelper; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; +import java.util.logging.Logger; import java.util.regex.Pattern; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link Utils}. @@ -49,6 +56,49 @@ public class UtilsTest { TestHelper.validateHasOnlyPrivateEmptyConstructor(Utils.class); } + @Test + public void shouldLogAndSendMessage() { + // given + Logger logger = TestHelper.setupLogger(); + Player player = mock(Player.class); + String message = "Finished adding foo to the bar"; + + // when + Utils.logAndSendMessage(player, message); + + // then + verify(logger).info(message); + verify(player).sendMessage(message); + } + + @Test + public void shouldHandleNullAsCommandSender() { + // given + Logger logger = TestHelper.setupLogger(); + String message = "Test test, test."; + + // when + Utils.logAndSendMessage(null, message); + + // then + verify(logger).info(message); + } + + @Test + public void shouldNotSendToCommandSenderTwice() { + // given + Logger logger = TestHelper.setupLogger(); + CommandSender sender = mock(ConsoleCommandSender.class); + String message = "Test test, test."; + + // when + Utils.logAndSendMessage(sender, message); + + // then + verify(logger).info(message); + verifyZeroInteractions(sender); + } + @Test public void shouldCheckIfClassIsLoaded() { // given / when / then From fd18930286ca65284dfd0d15f08b3f018871542b Mon Sep 17 00:00:00 2001 From: rafael59r2 Date: Sun, 12 Mar 2017 17:44:29 +0000 Subject: [PATCH 72/79] Translate messages_pt.yml (#230) * Update messages_pt.yml * Update messages_pt.yml * Update messages_pt.yml * Update messages_pt.yml --- src/main/resources/messages/messages_pt.yml | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index fef35f74..56ad1732 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -2,9 +2,9 @@ reg_msg: '&cPor favor registe-se com "/register password confirmePassword"' usage_reg: '&cUse: /register seu@email.com seu@email.com' reg_only: '&fApenas jogadores registados! Visite http://example.com para se registar' -# TODO kicked_admin_registered: 'An admin just registered you; please log in again' +kicked_admin_registered: 'Um administrador registou-te; por favor entre novamente' registered: '&cRegistado com sucesso!' -reg_disabled: '&cRegito de novos utilizadores desactivado' +reg_disabled: '&cRegisto de novos utilizadores desactivado' user_regged: '&cUtilizador já registado' # Password errors on registration @@ -26,11 +26,11 @@ unknown_user: '&cUsername não registado' denied_command: '&cPara utilizar este comando é necessário estar logado!' denied_chat: '&cPara usar o chat deve estar logado!' not_logged_in: '&cNão autenticado!' -# TODO tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.' +tempban_max_logins: '&cVocê foi temporariamente banido por falhar muitas vezes o login.' # TODO: Missing tags %reg_names max_reg: '&cAtingiu o numero máximo de %reg_count contas registas, maximo de contas %max_acc' no_perm: '&cSem Permissões' -error: '&fOcorreu um erro; Por favor contacte um admin' +error: '&fOcorreu um erro; Por favor contacte um administrador' unsafe_spawn: '&fA sua localização na saída não é segura, será tele-portado para a Spawn' kick_forvip: '&cUm jogador VIP entrou no servidor cheio!' @@ -41,11 +41,11 @@ antibot_auto_disabled: '[AuthMe] AntiBotMod desactivado automaticamente após %m # Other messages unregistered: '&cRegisto eliminado com sucesso!' -# TODO accounts_owned_self: 'You own %count accounts:' -# TODO accounts_owned_other: 'The player %name has %count accounts:' +accounts_owned_self: 'Você possui %count contas:' +accounts_owned_other: 'O jogador %name possui %count contas:' two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo a partir daqui %url' -# TODO recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -# TODO recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' +recovery_code_sent: 'O codigo para redefinir a senha foi enviado para o seu e-mail.' +recovery_code_incorrect: 'O codigo de recuperação está incorreto! Use "/email recovery [email]" para gerar um novo' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' usage_unreg: '&cUse: /unregister password' pwd_changed: '&cPassword alterada!' @@ -80,14 +80,14 @@ email_confirm: 'Confirme o seu email!' email_changed: 'Email alterado com sucesso!' email_send: 'Nova palavra-passe enviada para o seu email!' email_exists: '&cUm e-mail de recuperação já foi enviado! Pode descartá-lo e enviar um novo usando o comando abaixo:' -# TODO email_show: '&2Your current email address is: &f%email' -# TODO incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' +email_show: '&2O seu endereço de email atual é &f%email' +incomplete_email_settings: 'Erro: nem todas as definições necessarias para enviar email foram preenchidas. Por favor contate um administrador.' email_already_used: '&4O endereço de e-mail já está sendo usado' -# TODO email_send_failure: 'The email could not be sent. Please contact an administrator.' -# TODO show_no_email: '&2You currently don''t have email address associated with this account.' +email_send_failure: 'Não foi possivel enviar o email. Por favor contate um administrador.' +show_no_email: '&2Você atualmente não tem um endereço de email associado a essa conta.' add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail' recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery ' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente' # Captcha usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' @@ -95,11 +95,11 @@ wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'dia' +days: 'dias' From b5451df9d7048bf820f3816e356789c9bba3e4de Mon Sep 17 00:00:00 2001 From: rafael59r2 Date: Mon, 13 Mar 2017 06:31:30 +0000 Subject: [PATCH 73/79] Fix translations messages_pt.yml (#232) * Fix translation * Update messages_pt.yml --- src/main/resources/messages/messages_pt.yml | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 56ad1732..c926a912 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -1,8 +1,8 @@ # Registration -reg_msg: '&cPor favor registe-se com "/register password confirmePassword"' -usage_reg: '&cUse: /register seu@email.com seu@email.com' -reg_only: '&fApenas jogadores registados! Visite http://example.com para se registar' -kicked_admin_registered: 'Um administrador registou-te; por favor entre novamente' +reg_msg: '&cPor favor registe-se com "/register "' +usage_reg: '&cUse: /register ' +reg_only: '&fApenas jogadores registados podem entrar no servidor! Visite http://example.com para se registar' +kicked_admin_registered: 'Um administrador registou-te, por favor entre novamente' registered: '&cRegistado com sucesso!' reg_disabled: '&cRegisto de novos utilizadores desactivado' user_regged: '&cUtilizador já registado' @@ -11,14 +11,14 @@ user_regged: '&cUtilizador já registado' password_error: '&fAs passwords não coincidem' password_error_nick: '&cNão pode o usar seu nome como senha, por favor, escolha outra ...' password_error_unsafe: '&cA senha escolhida não é segura, por favor, escolha outra ...' -password_error_chars: '&4Sua senha contém caracteres ilegais. caracteres permitidos: REG_EX' -pass_len: '&fPassword demasiado curta' +password_error_chars: '&4Sua senha contém caracteres ilegais. Caracteres permitidos: REG_EX' +pass_len: '&fPassword demasiado curta ou longa! Por favor escolhe outra outra!' # Login -usage_log: '&cUse: /login password' +usage_log: '&cUse: /login ' wrong_pwd: '&cPassword errada!' login: '&bAutenticado com sucesso!' -login_msg: '&cIdentifique-se com "/login password"' +login_msg: '&cIdentifique-se com "/login "' timeout: '&fExcedeu o tempo para autenticação' # Errors @@ -47,12 +47,12 @@ two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo recovery_code_sent: 'O codigo para redefinir a senha foi enviado para o seu e-mail.' recovery_code_incorrect: 'O codigo de recuperação está incorreto! Use "/email recovery [email]" para gerar um novo' vb_nonActiv: '&fA sua conta não foi ainda activada, verifique o seu email onde irá receber indicações para activação de conta. ' -usage_unreg: '&cUse: /unregister password' +usage_unreg: '&cUse: /unregister ' pwd_changed: '&cPassword alterada!' logged_in: '&cJá se encontra autenticado!' logout: '&cSaida com sucesso' reload: '&fConfiguração e base de dados foram recarregadas' -usage_changepassword: '&fUse: /changepassword passwordAntiga passwordNova' +usage_changepassword: '&fUse: /changepassword ' # Session messages invalid_session: '&fDados de sessão não correspondem. Por favor aguarde o fim da sessão' @@ -85,13 +85,13 @@ incomplete_email_settings: 'Erro: nem todas as definições necessarias para env email_already_used: '&4O endereço de e-mail já está sendo usado' email_send_failure: 'Não foi possivel enviar o email. Por favor contate um administrador.' show_no_email: '&2Você atualmente não tem um endereço de email associado a essa conta.' -add_email: '&cPor favor adicione o seu email com : /email add seuEmail confirmarSeuEmail' +add_email: '&cPor favor adicione o seu email com : /email add ' recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recovery ' email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente' # Captcha -usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' -wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' +usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha THE_CAPTCHA' +wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha ' valid_captcha: '&cO seu captcha é válido!' # Time units From 62368b1cdab9513f48cff2a0d5c0500b08e34314 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 13 Mar 2017 20:34:55 +0100 Subject: [PATCH 74/79] Fix inverse tags in messages_pt; update translations doc page --- docs/translations.md | 17 ++++++++--------- src/main/resources/messages/messages_pt.yml | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/translations.md b/docs/translations.md index 70c26d8b..689c303d 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -1,5 +1,5 @@ - + # AuthMe Translations The following translations are available in AuthMe. Set `messagesLanguage` to the language code @@ -8,25 +8,25 @@ in your config.yml to use the language, or use another language code to start a Code | Language | Translated |   ---- | -------- | ---------: | ------ [en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | bar -[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 61% | bar +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 100% | bar [br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% | bar [cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% | bar [de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% | bar -[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 89% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | bar [eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% | bar [fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% | bar -[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | bar [gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% | bar [hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% | bar [id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% | bar -[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 89% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | bar [ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | bar [lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | bar [nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | bar [pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | bar -[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 77% | bar +[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | bar [ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% | bar -[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 89% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | bar [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | bar [tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | bar [uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% | bar @@ -36,7 +36,6 @@ Code | Language | Translated |   [zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | bar [zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | bar - --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Feb 28 19:25:18 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Mar 13 20:34:31 CET 2017 diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index c926a912..dfc51087 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -90,8 +90,8 @@ recovery_email: '&cPerdeu a sua password? Para a recuperar escreva /email recove email_cooldown_error: '&cUm email já foi enviado recentemente.Por favor, espere %time antes de enviar novamente' # Captcha -usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha THE_CAPTCHA' -wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha ' +usage_captcha: '&cPrecisa digitar um captcha, escreva: /captcha ' +wrong_captcha: '&cCaptcha errado, por favor escreva: /captcha THE_CAPTCHA' valid_captcha: '&cO seu captcha é válido!' # Time units From 1da74cb987c0c8c472d0373f2a730eea549c430a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 14 Mar 2017 22:26:19 +0100 Subject: [PATCH 75/79] #1005 Improve restricted user feature (performance, error handling) - Move check for restricted user into validation service - Keep restrictions in a map by name for fast lookup, avoid splitting Strings on every call - Gracefully handle case when entry does not have the expected ';' and log exception --- .../authme/process/join/AsynchronousJoin.java | 36 ++------- .../authme/service/ValidationService.java | 48 +++++++++++ .../properties/RestrictionSettings.java | 2 +- .../fr/xephi/authme/util/StringUtils.java | 16 ++++ .../authme/service/ValidationServiceTest.java | 81 ++++++++++++++++--- .../fr/xephi/authme/util/StringUtilsTest.java | 10 +++ 6 files changed, 148 insertions(+), 45 deletions(-) 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 3dcfe8e0..73bef42a 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -15,6 +15,7 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PluginHookService; +import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -71,6 +72,9 @@ public class AsynchronousJoin implements AsynchronousProcess { @Inject private CommandManager commandManager; + @Inject + private ValidationService validationService; + AsynchronousJoin() { } @@ -91,7 +95,7 @@ public class AsynchronousJoin implements AsynchronousProcess { pluginHookService.setEssentialsSocialSpyStatus(player, false); } - if (isNameRestricted(name, ip, player.getAddress().getHostName())) { + if (!validationService.fulfillsNameRestrictions(player)) { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { @@ -180,36 +184,6 @@ public class AsynchronousJoin implements AsynchronousProcess { limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); } - /** - * Returns whether the name is restricted based on the restriction settings. - * - * @param name The name to check - * @param ip The IP address of the player - * @param domain The hostname of the IP address - * - * @return True if the name is restricted (IP/domain is not allowed for the given name), - * false if the restrictions are met or if the name has no restrictions to it - */ - private boolean isNameRestricted(String name, String ip, String domain) { - if (!service.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)) { - return false; - } - - boolean nameFound = false; - for (String entry : service.getProperty(RestrictionSettings.ALLOWED_RESTRICTED_USERS)) { - String[] args = entry.split(";"); - String testName = args[0]; - String testIp = args[1]; - if (testName.equalsIgnoreCase(name)) { - nameFound = true; - if ((ip != null && testIp.equals(ip)) || (domain != null && testIp.equalsIgnoreCase(domain))) { - return false; - } - } - } - return nameFound; - } - /** * Checks whether the maximum number of accounts has been exceeded for the given IP address (according to * settings and permissions). If this is the case, the player is kicked. diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java index dd1f6e51..9511c6e3 100644 --- a/src/main/java/fr/xephi/authme/service/ValidationService.java +++ b/src/main/java/fr/xephi/authme/service/ValidationService.java @@ -1,6 +1,8 @@ package fr.xephi.authme.service; import ch.jalu.configme.properties.Property; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; @@ -12,8 +14,10 @@ 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 fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -23,6 +27,8 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import static fr.xephi.authme.util.StringUtils.isInsideString; + /** * Validation service. */ @@ -39,6 +45,7 @@ public class ValidationService implements Reloadable { private Pattern passwordRegex; private Set unrestrictedNames; + private Multimap restrictedNames; ValidationService() { } @@ -49,6 +56,9 @@ public class ValidationService implements Reloadable { passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)); // Use Set for more efficient contains() lookup unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)); + restrictedNames = settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS) + ? loadNameRestrictions(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + : HashMultimap.create(); } /** @@ -132,6 +142,24 @@ public class ValidationService implements Reloadable { return unrestrictedNames.contains(name.toLowerCase()); } + /** + * Checks that the player meets any name restriction if present (IP/domain-based). + * + * @param player the player to check + * @return true if the player may join, false if the player does not satisfy the name restrictions + */ + public boolean fulfillsNameRestrictions(Player player) { + Collection restrictions = restrictedNames.get(player.getName().toLowerCase()); + if (Utils.isCollectionEmpty(restrictions)) { + return true; + } + + String ip = PlayerUtils.getPlayerIp(player); + String domain = player.getAddress().getHostName(); + return restrictions.stream() + .anyMatch(restriction -> ip.equals(restriction) || domain.equalsIgnoreCase(restriction)); + } + /** * 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 @@ -161,6 +189,26 @@ public class ValidationService implements Reloadable { return false; } + /** + * Loads the configured name restrictions into a Multimap by player name (all-lowercase). + * + * @param configuredRestrictions the restriction rules to convert to a map + * @return map of allowed IPs/domain names by player name + */ + private Multimap loadNameRestrictions(List configuredRestrictions) { + Multimap restrictions = HashMultimap.create(); + for (String restriction : configuredRestrictions) { + if (isInsideString(';', restriction)) { + String[] data = restriction.split(";"); + restrictions.put(data[0].toLowerCase(), data[1]); + } else { + ConsoleLogger.warning("Restricted user rule must have a ';' separating name from restriction," + + " but found: '" + restriction + "'"); + } + } + return restrictions; + } + public static final class ValidationResult { private final MessageKey messageKey; private final String[] args; diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index 5a007b32..40de8ca6 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -81,7 +81,7 @@ public final class RestrictionSettings implements SettingsHolder { "Example:", " AllowedRestrictedUser:", " - playername;127.0.0.1"}) - public static final Property> ALLOWED_RESTRICTED_USERS = + public static final Property> RESTRICTED_USERS = newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); @Comment("Ban unknown IPs trying to log in with a restricted username?") diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java index 5e0696af..1f200c0f 100644 --- a/src/main/java/fr/xephi/authme/util/StringUtils.java +++ b/src/main/java/fr/xephi/authme/util/StringUtils.java @@ -76,4 +76,20 @@ public final class StringUtils { public static String formatException(Throwable th) { return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage(); } + + /** + * Check that the given needle is in the middle of the haystack, i.e. that the haystack + * contains the needle and that it is not at the very start or end. + * + * @param needle the needle to search for + * @param haystack the haystack to search in + * + * @return true if the needle is in the middle of the word, false otherwise + */ + // Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like + // isInsideString("11", "2211") would unexpectedly return true... + public static boolean isInsideString(char needle, String haystack) { + int index = haystack.indexOf(needle); + return index > 0 && index < haystack.length() - 1; + } } diff --git a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java index 21ffa952..3953d4ef 100644 --- a/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/ValidationServiceTest.java @@ -4,24 +4,30 @@ import ch.jalu.injector.testing.BeforeInjecting; import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import com.google.common.base.Strings; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.service.ValidationService.ValidationResult; import fr.xephi.authme.settings.Settings; 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 fr.xephi.authme.service.ValidationService.ValidationResult; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import java.util.Arrays; import java.util.Collections; +import java.util.logging.Logger; import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; @@ -55,6 +61,7 @@ public class ValidationServiceTest { .willReturn(asList("unsafe", "other-unsafe")); given(settings.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); given(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)).willReturn(asList("name01", "npc")); + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(false); } @Test @@ -115,8 +122,8 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailWithEmptyLists() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("test@example.org"); @@ -130,7 +137,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("TesT@Example.com"); @@ -144,7 +151,7 @@ public class ValidationServiceTest { // given given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)) .willReturn(asList("domain.tld", "example.com")); - given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.validateEmail("email@other-domain.abc"); @@ -156,7 +163,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptEmailNotInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -170,7 +177,7 @@ public class ValidationServiceTest { @Test public void shouldRejectEmailInBlacklist() { // given - given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(EmailSettings.DOMAIN_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(EmailSettings.DOMAIN_BLACKLIST)) .willReturn(asList("Example.org", "a-test-name.tld")); @@ -263,8 +270,8 @@ public class ValidationServiceTest { @Test public void shouldNotInvokeGeoLiteApiIfCountryListsAreEmpty() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); // when boolean result = validationService.isCountryAdmitted("addr"); @@ -278,7 +285,7 @@ public class ValidationServiceTest { public void shouldAcceptCountryInWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("CH"); @@ -294,7 +301,7 @@ public class ValidationServiceTest { public void shouldRejectCountryMissingFromWhitelist() { // given given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(asList("ch", "it")); - given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(Collections.emptyList()); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -309,7 +316,7 @@ public class ValidationServiceTest { @Test public void shouldAcceptCountryAbsentFromBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "127.0.0.1"; given(geoIpService.getCountryCode(ip)).willReturn("BR"); @@ -325,7 +332,7 @@ public class ValidationServiceTest { @Test public void shouldRejectCountryInBlacklist() { // given - given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); + given(settings.getProperty(ProtectionSettings.COUNTRIES_WHITELIST)).willReturn(Collections.emptyList()); given(settings.getProperty(ProtectionSettings.COUNTRIES_BLACKLIST)).willReturn(asList("ch", "it")); String ip = "123.45.67.89"; given(geoIpService.getCountryCode(ip)).willReturn("IT"); @@ -338,6 +345,54 @@ public class ValidationServiceTest { verify(geoIpService).getCountryCode(ip); } + @Test + public void shouldCheckNameRestrictions() { + // given + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); + given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;32.24.16.8")); + validationService.reload(); + + Player bobby = mockPlayer("bobby", "127.0.0.4"); + Player tamara = mockPlayer("taMARA", "8.8.8.8"); + Player notRestricted = mockPlayer("notRestricted", "0.0.0.0"); + + // when + boolean isBobbyAdmitted = validationService.fulfillsNameRestrictions(bobby); + boolean isTamaraAdmitted = validationService.fulfillsNameRestrictions(tamara); + boolean isNotRestrictedAdmitted = validationService.fulfillsNameRestrictions(notRestricted); + + // then + assertThat(isBobbyAdmitted, equalTo(true)); + assertThat(isTamaraAdmitted, equalTo(false)); + assertThat(isNotRestrictedAdmitted, equalTo(true)); + } + + @Test + public void shouldLogWarningForInvalidRestrictionRule() { + // given + Logger logger = TestHelper.setupLogger(); + given(settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)).willReturn(true); + given(settings.getProperty(RestrictionSettings.RESTRICTED_USERS)) + .willReturn(Arrays.asList("Bobby;127.0.0.4", "Tamara;")); + + // when + validationService.reload(); + + // then + ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); + verify(logger).warning(stringCaptor.capture()); + assertThat(stringCaptor.getValue(), containsString("Tamara;")); + } + + private static Player mockPlayer(String name, String ip) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + TestHelper.mockPlayerIp(player, ip); + given(player.getAddress().getHostName()).willReturn("--"); + return player; + } + private static void assertErrorEquals(ValidationResult validationResult, MessageKey messageKey, String... args) { assertThat(validationResult.hasError(), equalTo(true)); assertThat(validationResult.getMessageKey(), equalTo(messageKey)); diff --git a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java index 629adbd4..7111f81b 100644 --- a/src/test/java/fr/xephi/authme/util/StringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/StringUtilsTest.java @@ -95,4 +95,14 @@ public class StringUtilsTest { public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(StringUtils.class); } + + @Test + public void shouldCheckIfHasNeedleInWord() { + // given/when/then + assertThat(StringUtils.isInsideString('@', "@hello"), equalTo(false)); + assertThat(StringUtils.isInsideString('?', "absent"), equalTo(false)); + assertThat(StringUtils.isInsideString('-', "abcd-"), equalTo(false)); + assertThat(StringUtils.isInsideString('@', "hello@example"), equalTo(true)); + assertThat(StringUtils.isInsideString('@', "D@Z"), equalTo(true)); + } } From 457c07b53f57e6e65c666bdb02ba08754da1c859 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 15 Mar 2017 08:24:40 +0100 Subject: [PATCH 76/79] Consistency test: check that all values are mentioned for enum properties --- .../settings/properties/DatabaseSettings.java | 2 +- .../settings/SettingsConsistencyTest.java | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java index 378bd198..1e4697bf 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java @@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class DatabaseSettings implements SettingsHolder { @Comment({"What type of database do you want to use?", - "Valid values: sqlite, mysql"}) + "Valid values: SQLITE, MYSQL"}) public static final Property BACKEND = newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index 88724a84..dc5340ae 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -1,15 +1,25 @@ package fr.xephi.authme.settings; import ch.jalu.configme.configurationdata.ConfigurationData; +import ch.jalu.configme.properties.EnumProperty; import ch.jalu.configme.properties.Property; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.BeforeClass; import org.junit.Test; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import static fr.xephi.authme.ReflectionTestUtils.getFieldValue; import static org.junit.Assert.fail; /** @@ -22,6 +32,17 @@ public class SettingsConsistencyTest { */ private static final int MAX_COMMENT_LENGTH = 90; + /** + * Exclusions for the enum in comments check. Use {@link Exclude#ALL} + * to skip an entire property from being checked. + */ + private static final Multimap, Enum> EXCLUDED_ENUMS = + ImmutableSetMultimap., Enum>builder() + .put(DatabaseSettings.BACKEND, DataSourceType.FILE) + .put(SecuritySettings.PASSWORD_HASH, Exclude.ALL) + .put(SecuritySettings.LEGACY_HASHES, Exclude.ALL) + .build(); + private static ConfigurationData configurationData; @BeforeClass @@ -66,4 +87,63 @@ public class SettingsConsistencyTest { } } + /** + * Checks that enum properties have all possible enum values listed in their comment + * so the user knows which values are available. + */ + @Test + public void shouldMentionAllEnumValues() { + // given + Map, Enum> invalidEnumProperties = new HashMap<>(); + + for (Property property : configurationData.getProperties()) { + // when + Class> enumClass = getEnumClass(property); + if (enumClass != null) { + String comments = String.join("\n", configurationData.getCommentsForSection(property.getPath())); + Arrays.stream(enumClass.getEnumConstants()) + .filter(e -> !comments.contains(e.name()) && !isExcluded(property, e)) + .findFirst() + .ifPresent(e -> invalidEnumProperties.put(property, e)); + } + } + + // then + if (!invalidEnumProperties.isEmpty()) { + String invalidEnums = invalidEnumProperties.entrySet().stream() + .map(e -> e.getKey() + " does not mention " + e.getValue() + " and possibly others") + .collect(Collectors.joining("\n- ")); + + fail("Found enum properties that do not list all entries in the comments:\n- " + invalidEnums); + } + } + + /** + * Returns the enum class the property holds values for, if applicable. + * + * @param property the property to get the enum class from + * @return the enum class, or null if not available + */ + private static Class> getEnumClass(Property property) { + if (property instanceof EnumProperty) { + return getFieldValue(EnumProperty.class, (EnumProperty) property, "clazz"); + } else if (property instanceof EnumSetProperty) { + return getFieldValue(EnumSetProperty.class, (EnumSetProperty) property, "enumClass"); + } + return null; + } + + private static boolean isExcluded(Property property, Enum enumValue) { + return EXCLUDED_ENUMS.get(property).contains(Exclude.ALL) + || EXCLUDED_ENUMS.get(property).contains(enumValue); + } + + /** + * Dummy enum to specify in the exclusion that all enum values + * should be skipped. See its usages. + */ + private enum Exclude { + ALL + } + } From 45f02f6a31928386e87a09435a101f18bda191cb Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Wed, 15 Mar 2017 18:01:00 +0100 Subject: [PATCH 77/79] Check java version on initialize #1096 --- src/main/java/fr/xephi/authme/AuthMe.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 21b9bcb5..58781c7e 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -36,6 +36,7 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.purge.PurgeService; +import org.apache.commons.lang.SystemUtils; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -195,6 +196,11 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.setLogger(getLogger()); ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME)); + // Check java version + if(SystemUtils.isJavaVersionAtLeast(1.8f)) { + throw new IllegalStateException("You need Java 1.8 or above to run this plugin!"); + } + // Create plugin folder getDataFolder().mkdir(); From 405d472ba81e0932aef9bf25f286b86c3733e70f Mon Sep 17 00:00:00 2001 From: Playhi <000902play@gmail.com> Date: Fri, 17 Mar 2017 08:31:23 -0500 Subject: [PATCH 78/79] Update messages_zhcn.yml (#234) --- src/main/resources/messages/messages_zhcn.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 63922334..65f3fe16 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -87,7 +87,7 @@ email_send_failure: '邮件发送失败,请联系管理员' show_no_email: '&2您当前并没有任何邮箱与该账号绑定' add_email: '&8[&6玩家系统&8] &c请输入“/email add <你的邮箱> <再输入一次以确认>”以把你的邮箱添加到此帐号' recovery_email: '&8[&6玩家系统&8] &c忘了你的密码?请输入:“/email recovery <你的邮箱>”' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +email_cooldown_error: '&c邮件已在几分钟前发送,您需要等待 %time 后才能再次请求发送' # Captcha usage_captcha: '&8[&6玩家系统&8] &c正确用法:/captcha ' @@ -95,11 +95,11 @@ wrong_captcha: '&8[&6玩家系统&8] &c错误的验证码,请输入:“/capt valid_captcha: '&8[&6玩家系统&8] &c你的验证码是有效的!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: '秒' +seconds: '秒' +minute: '分钟' +minutes: '分钟' +hour: '小时' +hours: '小时' +day: '天' +days: '天' From 17415493f541256c40988f68c5b4a472fe3e2968 Mon Sep 17 00:00:00 2001 From: Gabriele C Date: Fri, 17 Mar 2017 14:34:24 +0100 Subject: [PATCH 79/79] Fix wrong logic in the java version check --- src/main/java/fr/xephi/authme/AuthMe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 58781c7e..1fbc8996 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -197,7 +197,7 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME)); // Check java version - if(SystemUtils.isJavaVersionAtLeast(1.8f)) { + if(!SystemUtils.isJavaVersionAtLeast(1.8f)) { throw new IllegalStateException("You need Java 1.8 or above to run this plugin!"); }