From 8aa1bb993567f42943148f50b66514f3c5669e60 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Jan 2017 14:14:32 +0100 Subject: [PATCH 001/125] Open 5.3 development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0648d9818..37e5f505b 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 002/125] 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 b6c006f9b..18463bcaa 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 8e2b1bdc1..c5cf91c0f 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 27fa6c2e9..af4d5beb7 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 8ef7b1daa..b6e338ec0 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 7a1d19fa4..fed077682 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 2388fc608..000000000 --- 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 b3ec53c7b..b7628147b 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 f359f0883..000000000 --- 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 fa8ee9c6d..9d65047c5 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 003/125] #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 74a986543..1b987080b 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 d64bda776..6fb5fa8b3 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 d287e140c..980f2adde 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 000000000..b2cc9812e --- /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 000000000..4489f7a7c --- /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 42326771e..f7d2604c2 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 000000000..010761a06 --- /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 004/125] #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 b2cc9812e..8fcb21638 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 000000000..3c476237d --- /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 000000000..08ece1c3a --- /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 4489f7a7c..5129777db 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 000000000..dc8eaef9d --- /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 010761a06..37a3386af 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 005/125] 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 597f89a76..6f8695644 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 006/125] #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 8fcb21638..60666f7a3 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 bc0abf38a..d87ebd5e3 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 3c476237d..5fb0cd3d3 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 08ece1c3a..a5bb58a26 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 5129777db..2c7c6ba53 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 dc8eaef9d..677b30e2f 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 000000000..660132fbc --- /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 000000000..92c3f70d4 --- /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 fb8e4e40f..3ec3b4473 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 73113ccc4..b99eef528 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 8c6bf79de..757f09ca3 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 2eef86b03..a1e9060f7 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 007/125] 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 37e5f505b..622af6bde 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 0dd421817..e939d11fc 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 000000000..714036ce3 --- /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 008/125] #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 537c3ebc5..050b908ad 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 9ab2a370c..dfedf8ee0 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 7ce0b3e5a..6af00070a 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 dab9cdc2c..0f7e819f8 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 17710df45..182291463 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 aea1b4be8..cebbcdcb2 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 fae428d00..0b06df61e 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 aac60dde5..e4b175b7d 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 b1eaa222b..f512d67b7 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 cfd717ebc..eb6ffd6d2 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 5d3d870ce..054651ad7 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 67893666f..da7c270be 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 5181b3f7c..65e2614ce 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 009/125] 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 6af00070a..497c99ab3 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 9f52214b9..849ecd657 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 71c7a2879..36f6497ce 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 c215b8d17..b7773e7a2 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 b11d00003..9b4550d84 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 8955569ec..79badc53e 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 498ba4a91..1de19f1d7 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 010/125] #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 7e01e0e30..98030c963 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 000000000..f9541deb4 --- /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 000000000..1f0389833 --- /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 000000000..e3876ff0a --- /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 aaeb0eea3..2160eea56 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 9ca68c314..f04829d41 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 a4e51e28c..23bb24587 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 011/125] 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 46292e160..b1841ef41 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 012/125] 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 893aba229..622ed4816 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 1b2270285..dd2790bc2 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 1e7862443..11e9d6afe 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 73db67f89..47bfb9126 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 91f80041d..90c62a24b 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 ac1742fa6..26f7d03b9 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 8eb8e9598..fcc454513 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 f9c5880ca..6d3468543 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 1ca06911a..edd561925 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 013/125] 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 f52f9b41b..e6c5e755f 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 014/125] 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 6cc57480f..7546a89c0 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 92c3f70d4..ce9487e21 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 30a632443..44306dc81 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 015/125] 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 000000000..20d6bd9ae --- /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 016/125] #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 a0d4e947c..5d19e8f40 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 987caeccc..5b809b2c9 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 86fa2db6f..5d777ab83 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 1a143c8e9..be4983ab7 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 292b0ec97..ea0f372a2 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 000000000..399fd93a7 --- /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 12949c5aa..94fe9a0df 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 017/125] 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 b01674591..9d18820cd 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 8eae45ec2..7d01a7db1 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 018/125] #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 622ed4816..b6fa29443 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 050b908ad..5e14e986c 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 6fb5fa8b3..70402e539 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 c181f83b0..cea51ea01 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 fcc454513..152f2e356 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 ff992bfeb..7927f826f 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 019/125] 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 0f7e819f8..3dcfe8e04 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 e4b175b7d..ade6833da 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 eb6ffd6d2..5f45ca5d9 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 020/125] 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 73c5eed61..4b3396b5b 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 ed877f87b..fde994af5 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 4e282728b..d0677579f 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 021/125] 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 622af6bde..d275b20dd 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 1b987080b..8ed9ffb36 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 394b6f144..083d8a531 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 efa27ff09..e1c0661ce 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 000000000..0f4ae62ad --- /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 000000000..04c11c68e --- /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 8549b388b..d29471089 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 aae925b51..89132fcfa 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 1166cef23..e343199fe 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 cf306599d..0474eac1a 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 e651ebea8..04d2bc0bb 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 6b2c9c8af..34756ebb3 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 0dbd1f4a9..6b5d510e0 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 022/125] #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 5e14e986c..c7a3b343a 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 023/125] Update libs --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d275b20dd..628bdb7d3 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 024/125] 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 f500ac072..f62b00e15 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 025/125] 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 628bdb7d3..ff67c0dfe 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 e939d11fc..e803fafa1 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 714036ce3..000000000 --- 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 026/125] 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 8ed9ffb36..128e96938 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 e803fafa1..349965083 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 027/125] 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 c5cf91c0f..33003c3ae 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 dd7bad6e4..3a23cdc8c 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 028/125] #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 f9541deb4..8e7168f2d 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 000000000..cfad4013a --- /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 a782d3a13..f5952e40b 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 029/125] #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 e7fa37ad0..fdc987a21 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 000000000..83beb37d6 --- /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 b06a01f82..576c5da36 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 000000000..d4a3f8684 --- /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 030/125] #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 e5d31ed1a..07019a725 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 83beb37d6..519946f89 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 000000000..59c54d4d6 --- /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 28c2ab9ee..4fd8ad117 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 d4a3f8684..cdc9c36a5 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 000000000..9903842de --- /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 031/125] 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 8e7168f2d..9d67da39a 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 032/125] #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 f86f54214..23da3afff 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 000000000..840d5911c --- /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 f01311af1..50b181783 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 000000000..f58e03122 --- /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 033/125] Add manifest file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 573eb0612..82df141db 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 034/125] #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 36f33a3cb..7e83ec23c 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 054651ad7..daed1a8c9 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 92be450da..d53091db9 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 035/125] #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 ff67c0dfe..8048970b3 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 f23a68a5d..e2001096c 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 b99eef528..73232d90d 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 036/125] #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 11557c096..7104cee2b 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 e2001096c..e0330bf9d 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 037/125] #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 605283ac2..cf4952a58 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 000000000..dae258dbe --- /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 038/125] 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 7c7302eb8..3c4e067b2 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 0ad582abe..db166a748 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 dae258dbe..e0c470b0e 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 039/125] #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 cf4952a58..7d46daf84 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 a4fc733eb..5e0696af9 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 e0c470b0e..f211a608b 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 1be95d0d8..629adbd40 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 040/125] 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 7e83ec23c..b328d5450 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 23da3afff..512941db6 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 07019a725..1d55fa8f5 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 fdc987a21..cae8aaa73 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 b7628147b..e7ec1657a 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 000000000..ecc86299e --- /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 519946f89..894b6486e 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 840d5911c..7e711673e 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 59c54d4d6..67839294c 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 d53091db9..6f20d830b 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 50b181783..738c1c158 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 4fd8ad117..ab1cbb0b5 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 576c5da36..eb8af0dc3 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 000000000..03a0f9d47 --- /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 cdc9c36a5..cdace3065 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 f58e03122..15a55aa09 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 9903842de..dcfcb73af 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 041/125] 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 cfad4013a..2a994246a 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 5dc16ac60..eb188f790 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 ac4bb4bd2..6a0a590c0 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 000000000..46343369f --- /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 f5952e40b..2b482b26a 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 88f8cd602..7a6e702d3 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 27166f95c..556a10331 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 76d1352d8..c87252e1c 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 000000000..9feb94237 --- /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 027aea57d..835d4b49b 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 aecc158a3..9ca9c7aa8 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 042/125] #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 eb188f790..b3fb3b62b 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 7546a89c0..e8c3939a9 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 5d777ab83..d7fdcec89 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 daed1a8c9..496fb3b2b 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 e7ec1657a..b7628147b 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 894b6486e..e920805c2 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 7e711673e..fea8fb313 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 67839294c..c3ae908cd 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 556a10331..5b1020780 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 94fe9a0df..8f48fffec 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 15a55aa09..42180ab26 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 043/125] #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 4b3396b5b..ff2d54fb4 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 dee35ed79..a4bc104ba 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 977e8eaf5..38e79face 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 a6117b3be..c213f49cb 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 8b653fb43..586a7376f 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 474c8237a..648a5153f 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 d1b147002..c71902341 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 e0c0c96dd..ac7415718 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 53b0704d6..05473e68a 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 bf359e8c9..a48ac74db 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 1bd884568..dd01ccc6f 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 2739a1a8f..1c0570306 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 f5667eb89..dc62f8c18 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 b4fde4abb..90db87bd8 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 cdf6fed82..ede276906 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 db7bc8aa5..a65d93526 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 a2bcbe257..d75dec4a1 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 2ee68f951..630fadd6e 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 3f9e37ae9..549efb9e1 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 7254f8bbf..fef35f74f 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 d1726d69b..703b93f59 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 873b63979..11f55835e 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 5eb97d929..b49f8cf5c 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 480e0e5b2..a3ba1f262 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 d1d53187f..35dbbffee 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 4cb86ae86..7f68be063 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 e6c5e755f..63922334d 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 a07d52490..896003d4f 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 1d9876ad8..92f9113ea 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 b5dc97cf2..b2cff0384 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 044/125] 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 be5425c23..d16a85478 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 c0f9cd0b5..b5200b01d 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 000000000..60b5b68ec --- /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 000000000..8c9a8187f --- /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 000000000..199150545 --- /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 045/125] #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 128e96938..21b9bcb50 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 2a994246a..b0abcd557 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 46343369f..cb7c86de6 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 2b482b26a..344c39c96 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 561ce511b..3a9ede5d9 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 046/125] 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 9d67da39a..c910cb929 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 047/125] 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 549efb9e1..35622e5d4 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 048/125] 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 b6fa29443..26883ca95 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 b551deeda..45fcd7ad8 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 dd2790bc2..1f077d2ae 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 152f2e356..8d307c25d 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 049/125] 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 db85b8581..6fb8ff856 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 050/125] #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 a4bc104ba..70c26d8bc 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 a3ba1f262..1f3795af7 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 051/125] 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 dd01ccc6f..9070db19d 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 052/125] 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 318b451e9..7d53ae799 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 053/125] 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 1f3795af7..3431e99e6 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 054/125] [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 ede276906..989b555ba 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 055/125] 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 11f55835e..ed4e1729f 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 056/125] #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 d2be78047..8ca506250 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 a52b216b6..1c67061f3 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 7e0bb1a51..102584edf 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 3ec28a7b6..556f58ef9 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 da5a13124..d0ac4aa57 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 057/125] 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 000000000..d8f9076d1 --- /dev/null +++ b/.checkstyle.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..ee4961678 --- /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 058/125] 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 c9c717c70..2138ed4f1 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 059/125] 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 ee4961678..9c4405f85 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 d05fbe6b9..75e746e6d 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 d4f34b2c6..2193f64c0 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 18463bcaa..57b92d735 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 c5756cda9..d7150c890 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 b6d732cdb..f984007e3 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 eb9d904cd..42a8ecf1b 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 349965083..8767e88a6 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 e3ff4ec5b..06a8761ab 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 48cf1868e..051507651 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 4476f80ad..daf686389 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 b7773e7a2..acae466c1 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 1de19f1d7..c86863f78 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 a79c63c82..9d40bcf82 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 7d4b3d952..d0dacda4d 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 698979d8a..23101e22a 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 14cf0dcb7..e945cebd6 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 162bf8aac..57bb5941f 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 ae289e546..d2b34c9ac 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 fde994af5..378bd1985 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 3a9ede5d9..f7522b94f 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 f512d67b7..d4e80a1c7 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 5f45ca5d9..d99ffa0b2 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 3a19a70e2..2bae7179c 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 0cfa029ac..2c62454c8 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 38615b789..7d87e77bd 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 d0677579f..5a007b32d 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 496fb3b2b..455af7e8b 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 db166a748..2dce3c64a 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 660132fbc..a9d193199 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 060/125] 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 57b92d735..e195c4752 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 22ccf582b84e60b005a794281dbdea188ddb8dfe Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 19:25:35 +0100 Subject: [PATCH 061/125] #1113 Create LimboService (work in progress) - Introduce new LimboService with a higher level abstraction for outside classes to trigger LimboPlayer actions - Add methods to LimboPlayerTaskManager for muting the MessagesTask safely --- .../authme/RegisterAdminCommand.java | 9 +-- .../executable/captcha/CaptchaCommand.java | 6 +- .../xephi/authme/data/limbo/LimboCache.java | 2 +- .../authme/data/limbo/LimboPlayerStorage.java | 2 +- .../xephi/authme/data/limbo/LimboService.java | 60 +++++++++++++++++++ .../initialization/OnShutdownPlayerSaver.java | 22 +++---- .../authme/permission/AuthGroupHandler.java | 6 +- .../authme/process/join/AsynchronousJoin.java | 11 ++-- .../process/login/AsynchronousLogin.java | 18 ++---- .../process/login/ProcessSyncPlayerLogin.java | 10 ++-- .../process/logout/AsynchronousLogout.java | 7 ++- .../ProcessSynchronousPlayerLogout.java | 2 +- .../quit/ProcessSyncronousPlayerQuit.java | 19 +----- .../register/ProcessSyncPasswordRegister.java | 5 -- .../unregister/AsynchronousUnregister.java | 14 ++--- .../authme/service/TeleportationService.java | 8 +-- .../authme/task/LimboPlayerTaskManager.java | 41 +++++++++++-- .../authme/RegisterAdminCommandTest.java | 4 +- .../captcha/CaptchaCommandTest.java | 14 ++--- .../AsynchronousUnregisterTest.java | 8 +-- .../task/LimboPlayerTaskManagerTest.java | 18 +++--- 21 files changed, 171 insertions(+), 115 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/LimboService.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index cba5edf0d..d4ad49466 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -1,9 +1,9 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -39,7 +39,7 @@ public class RegisterAdminCommand implements ExecutableCommand { private ValidationService validationService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Override public void executeCommand(final CommandSender sender, List arguments) { @@ -83,7 +83,8 @@ public class RegisterAdminCommand implements ExecutableCommand { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { - limboCache.restoreData(player); + // TODO #1113: Is it necessary to restore here or is this covered by the quit process? + limboService.restoreData(player); player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER)); } }); 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 d7150c890..e87264903 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,9 +3,9 @@ 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.data.limbo.LimboCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -23,7 +23,7 @@ public class CaptchaCommand extends PlayerCommand { private CommonService commonService; @Inject - private LimboCache limboCache; + private LimboPlayerTaskManager limboPlayerTaskManager; @Override public void runCommand(Player player, List arguments) { @@ -43,7 +43,7 @@ public class CaptchaCommand extends PlayerCommand { if (isCorrectCode) { commonService.send(player, MessageKey.CAPTCHA_SUCCESS); commonService.send(player, MessageKey.LOGIN_MESSAGE); - limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(false); + limboPlayerTaskManager.unmuteMessageTask(player); } else { String newCode = captchaManager.generateCode(player.getName()); commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); 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 26883ca95..99416636f 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java @@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; /** * Manages all {@link LimboPlayer} instances. */ -public class LimboCache { +class LimboCache { private final Map cache = new ConcurrentHashMap<>(); 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 1f077d2ae..4ab45a8d3 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java @@ -29,7 +29,7 @@ import java.nio.charset.StandardCharsets; /** * Class used to store player's data (OP, flying, speed, position) to disk. */ -public class LimboPlayerStorage { +class LimboPlayerStorage { private final Gson gson; private final File cacheDir; diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java new file mode 100644 index 000000000..0c3b2532d --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -0,0 +1,60 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Service for managing players that are in "limbo," a temporary state players are + * put in which have joined but not yet logged in yet. + */ +public class LimboService { + + @Inject + private LimboCache limboCache; + + LimboService() { + } + + + /** + * Restores the limbo data and subsequently deletes the entry. + * + * @param player the player whose data should be restored + */ + public void restoreData(Player player) { + // TODO #1113: Think about architecture for various "restore" strategies + limboCache.restoreData(player); + limboCache.deletePlayerData(player); + } + + /** + * Returns the limbo player for the given name, or null otherwise. + * + * @param name the name to retrieve the data for + * @return the associated limbo player, or null if none available + */ + public LimboPlayer getLimboPlayer(String name) { + return limboCache.getPlayerData(name); + } + + /** + * Returns whether there is a limbo player for the given name. + * + * @param name the name to check + * @return true if present, false otherwise + */ + public boolean hasLimboPlayer(String name) { + return limboCache.hasPlayerData(name); + } + + /** + * Creates a LimboPlayer for the given player. + * + * @param player the player to process + */ + public void createLimboPlayer(Player player) { + // TODO #1113: We should remove the player's data in here as well + limboCache.addPlayerData(player); + } +} diff --git a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java index 11e9d6afe..e22eb9c5f 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java +++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java @@ -2,15 +2,14 @@ package fr.xephi.authme.initialization; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboPlayerStorage; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.PluginHookService; +import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.ValidationService; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -28,17 +27,15 @@ public class OnShutdownPlayerSaver { @Inject private ValidationService validationService; @Inject - private LimboCache limboCache; - @Inject private DataSource dataSource; @Inject - private LimboPlayerStorage limboPlayerStorage; - @Inject private SpawnLoader spawnLoader; @Inject private PluginHookService pluginHookService; @Inject private PlayerCache playerCache; + @Inject + private LimboService limboService; OnShutdownPlayerSaver() { } @@ -57,9 +54,8 @@ public class OnShutdownPlayerSaver { if (pluginHookService.isNpc(player) || validationService.isUnrestricted(name)) { return; } - if (limboCache.hasPlayerData(name)) { - limboCache.restoreData(player); - limboCache.removeFromCache(player); + if (limboService.hasLimboPlayer(name)) { + limboService.restoreData(player); } else { saveLoggedinPlayer(player); } @@ -75,9 +71,5 @@ public class OnShutdownPlayerSaver { .location(loc).build(); dataSource.updateQuitLoc(auth); } - if (settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN) - && !settings.getProperty(RestrictionSettings.NO_TELEPORT) && !limboPlayerStorage.hasData(player)) { - limboPlayerStorage.saveData(player); - } } } diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index c7a3b343a..5019d316d 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -1,8 +1,8 @@ package fr.xephi.authme.permission; import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -33,7 +33,7 @@ public class AuthGroupHandler implements Reloadable { private Settings settings; @Inject - private LimboCache limboCache; + private LimboService limboService; private String unregisteredGroup; private String registeredGroup; @@ -53,7 +53,7 @@ public class AuthGroupHandler implements Reloadable { } String primaryGroup = Optional - .ofNullable(limboCache.getPlayerData(player.getName())) + .ofNullable(limboService.getLimboPlayer(player.getName())) .map(LimboPlayer::getGroup) .orElse(""); 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 3dcfe8e04..b03a70f8f 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -4,7 +4,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.SessionManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.ProtectInventoryEvent; import fr.xephi.authme.message.MessageKey; @@ -51,7 +51,7 @@ public class AsynchronousJoin implements AsynchronousProcess { private PlayerCache playerCache; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private SessionManager sessionManager; @@ -111,7 +111,7 @@ public class AsynchronousJoin implements AsynchronousProcess { final boolean isAuthAvailable = database.isAuthAvailable(name); if (isAuthAvailable) { - limboCache.addPlayerData(player); + limboService.createLimboPlayer(player); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); // Protect inventory @@ -141,9 +141,10 @@ public class AsynchronousJoin implements AsynchronousProcess { } } } else { + // TODO #1113: Why delete and add LimboPlayer again? // Not Registered. Delete old data, load default one. - limboCache.deletePlayerData(player); - limboCache.addPlayerData(player); + // limboCache.deletePlayerData(player); + // limboCache.addPlayerData(player); // Groups logic service.setGroup(player, AuthGroupType.UNREGISTERED); 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 6fb8ff856..1e16b660a 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -6,8 +6,6 @@ import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.TempbanManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; @@ -16,16 +14,16 @@ import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SyncProcessManager; import fr.xephi.authme.security.PasswordSecurity; +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.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; 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 fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; @@ -52,9 +50,6 @@ public class AsynchronousLogin implements AsynchronousProcess { @Inject private PlayerCache playerCache; - @Inject - private LimboCache limboCache; - @Inject private SyncProcessManager syncProcessManager; @@ -195,7 +190,7 @@ public class AsynchronousLogin implements AsynchronousProcess { // If the authentication fails check if Captcha is required and send a message to the player if (captchaManager.isCaptchaRequired(player.getName())) { - limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(true); + limboPlayerTaskManager.muteMessageTask(player); service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(player.getName())); } @@ -246,10 +241,7 @@ public class AsynchronousLogin implements AsynchronousProcess { // task, we schedule it in the end // so that we can be sure, and have not to care if it might be // processed in other order. - LimboPlayer limboPlayer = limboCache.getPlayerData(name); - if (limboPlayer != null) { - limboPlayer.clearTasks(); - } + limboPlayerTaskManager.clearTasks(player); syncProcessManager.processSyncPlayerLogin(player); } else { ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted..."); @@ -260,7 +252,7 @@ public class AsynchronousLogin implements AsynchronousProcess { int threshold = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD_THRESHOLD); String command = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD); - if(threshold < 2 || command.isEmpty()) { + if (threshold < 2 || command.isEmpty()) { return; } 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 70402e539..4cdef9949 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -1,8 +1,8 @@ package fr.xephi.authme.process.login; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.RestoreInventoryEvent; @@ -31,7 +31,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { private BungeeService bungeeService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private BukkitService bukkitService; @@ -65,14 +65,14 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { public void processPlayerLogin(Player player) { final String name = player.getName().toLowerCase(); - final LimboPlayer limbo = limboCache.getPlayerData(name); + final LimboPlayer limbo = limboService.getLimboPlayer(name); // Limbo contains the State of the Player before /login if (limbo != null) { - limboCache.restoreData(player); - limboCache.deletePlayerData(player); + limboService.restoreData(player); // do we really need to use location from database for now? // because LimboCache#restoreData teleport player to last location. } + // TODO #1113: Need to set group before deleting limboPlayer?? commonService.setGroup(player, AuthGroupType.LOGGED_IN); if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index efd3066aa..1120fd595 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -2,7 +2,7 @@ package fr.xephi.authme.process.logout; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.AsynchronousProcess; @@ -25,7 +25,7 @@ public class AsynchronousLogout implements AsynchronousProcess { private PlayerCache playerCache; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private SyncProcessManager syncProcessManager; @@ -47,7 +47,8 @@ public class AsynchronousLogout implements AsynchronousProcess { database.updateQuitLoc(auth); } - limboCache.addPlayerData(player); + // TODO #1113: Would we not have to RESTORE the limbo player here? + limboService.createLimboPlayer(player); playerCache.removePlayer(name); database.setUnlogged(name); syncProcessManager.processSyncPlayerLogout(player); 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 182291463..9f5dc32d3 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -71,8 +71,8 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { teleportationService.teleportOnJoin(player); // Apply Blindness effect - final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { + int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } 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 47bfb9126..c5f633e96 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java @@ -1,7 +1,6 @@ package fr.xephi.authme.process.quit; -import fr.xephi.authme.data.limbo.LimboPlayerStorage; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.process.SynchronousProcess; import org.bukkit.entity.Player; @@ -11,22 +10,10 @@ import javax.inject.Inject; public class ProcessSyncronousPlayerQuit implements SynchronousProcess { @Inject - private LimboPlayerStorage limboPlayerStorage; - - @Inject - private LimboCache limboCache; + private LimboService limboService; public void processSyncQuit(Player player) { - if (limboCache.hasPlayerData(player.getName())) { // it mean player is not authenticated - limboCache.restoreData(player); - limboCache.removeFromCache(player); - } else { - // Save player's data, so we could retrieve it later on player next join - if (!limboPlayerStorage.hasData(player)) { - limboPlayerStorage.saveData(player); - } - } - + limboService.restoreData(player); player.leaveVehicle(); } } 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 0b06df61e..c500f1556 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -1,7 +1,6 @@ package fr.xephi.authme.process.register; 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.process.SynchronousProcess; @@ -26,9 +25,6 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { @Inject private CommonService service; - @Inject - private LimboCache limboCache; - @Inject private LimboPlayerTaskManager limboPlayerTaskManager; @@ -45,7 +41,6 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { */ private void requestLogin(Player player) { final String name = player.getName().toLowerCase(); - limboCache.updatePlayerData(player); limboPlayerTaskManager.registerTimeoutTask(player); limboPlayerTaskManager.registerMessageTask(name, true); diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 764e56768..879edd181 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -3,19 +3,19 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupHandler; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.TeleportationService; 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.service.TeleportationService; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; @@ -43,7 +43,7 @@ public class AsynchronousUnregister implements AsynchronousProcess { private BukkitService bukkitService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private LimboPlayerTaskManager limboPlayerTaskManager; @@ -111,8 +111,8 @@ public class AsynchronousUnregister implements AsynchronousProcess { teleportationService.teleportOnJoin(player); player.saveData(); - limboCache.deletePlayerData(player); - limboCache.addPlayerData(player); + // TODO #1113: Why delete? limboCache.deletePlayerData(player); + limboService.createLimboPlayer(player); limboPlayerTaskManager.registerTimeoutTask(player); limboPlayerTaskManager.registerMessageTask(name, false); diff --git a/src/main/java/fr/xephi/authme/service/TeleportationService.java b/src/main/java/fr/xephi/authme/service/TeleportationService.java index 9be5012f4..65fd84044 100644 --- a/src/main/java/fr/xephi/authme/service/TeleportationService.java +++ b/src/main/java/fr/xephi/authme/service/TeleportationService.java @@ -99,19 +99,19 @@ public class TeleportationService implements Reloadable { * * @param player the player * @param auth corresponding PlayerAuth object - * @param limbo corresponding PlayerData object + * @param limbo corresponding LimboPlayer object */ public void teleportOnLogin(final Player player, PlayerAuth auth, LimboPlayer limbo) { if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) { return; } - // #856: If PlayerData comes from a persisted file, the Location might be null + // #856: If LimboPlayer comes from a persisted file, the Location might be null String worldName = (limbo != null && limbo.getLocation() != null) ? limbo.getLocation().getWorld().getName() : null; - // The world in PlayerData is from where the player comes, before any teleportation by AuthMe + // The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe if (mustForceSpawnAfterLogin(worldName)) { teleportToSpawn(player, true); } else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) { @@ -148,7 +148,7 @@ public class TeleportationService implements Reloadable { /** * Emits the teleportation event and performs teleportation according to it (potentially modified - * by external listeners). Note that not teleportation is performed if the event's location is empty. + * by external listeners). Note that no teleportation is performed if the event's location is empty. * * @param player the player to teleport * @param event the event to emit and according to which to teleport diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java index 234565670..25e118634 100644 --- a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java +++ b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java @@ -2,8 +2,8 @@ package fr.xephi.authme.task; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; @@ -33,7 +33,7 @@ public class LimboPlayerTaskManager { private BukkitService bukkitService; @Inject - private LimboCache limboCache; + private LimboService limboService; @Inject private PlayerCache playerCache; @@ -53,7 +53,7 @@ public class LimboPlayerTaskManager { final int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL); final MessageKey key = getMessageKey(isRegistered); if (interval > 0) { - final LimboPlayer limboPlayer = limboCache.getPlayerData(name); + final LimboPlayer limboPlayer = limboService.getLimboPlayer(name); if (limboPlayer == null) { ConsoleLogger.info("PlayerData for '" + name + "' is not available"); } else { @@ -73,7 +73,7 @@ public class LimboPlayerTaskManager { public void registerTimeoutTask(Player player) { final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; if (timeout > 0) { - final LimboPlayer limboPlayer = limboCache.getPlayerData(player.getName()); + final LimboPlayer limboPlayer = limboService.getLimboPlayer(player.getName()); if (limboPlayer == null) { ConsoleLogger.info("PlayerData for '" + player.getName() + "' is not available"); } else { @@ -85,6 +85,27 @@ public class LimboPlayerTaskManager { } } + public void muteMessageTask(Player player) { + LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); + if (limbo != null) { + setMuted(limbo.getMessageTask(), true); + } + } + + public void unmuteMessageTask(Player player) { + LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); + if (limbo != null) { + setMuted(limbo.getMessageTask(), false); + } + } + + public void clearTasks(Player player) { + LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); + if (limbo != null) { + limbo.clearTasks(); + } + } + /** * Returns the appropriate message key according to the registration status and settings. * @@ -120,4 +141,16 @@ public class LimboPlayerTaskManager { task.cancel(); } } + + /** + * Null-safe method to set the muted flag on a message task. + * + * @param task the task to modify (or null) + * @param isMuted the value to set if task is not null + */ + private static void setMuted(MessageTask task, boolean isMuted) { + if (task != null) { + task.setMuted(isMuted); + } + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java index 914199120..ebe50a745 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java @@ -2,7 +2,7 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -57,7 +57,7 @@ public class RegisterAdminCommandTest { private ValidationService validationService; @Mock - private LimboCache limboCache; + private LimboService limboService; @BeforeClass public static void setUpLogger() { diff --git a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java index dd9240058..5ba141e2c 100644 --- a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java @@ -2,11 +2,9 @@ package fr.xephi.authme.command.executable.captcha; import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.data.limbo.LimboCache; -import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,7 +38,7 @@ public class CaptchaCommandTest { private CommonService commandService; @Mock - private LimboCache limboCache; + private LimboPlayerTaskManager limboPlayerTaskManager; @Test public void shouldDetectIfPlayerIsLoggedIn() { @@ -82,10 +80,6 @@ public class CaptchaCommandTest { given(captchaManager.isCaptchaRequired(name)).willReturn(true); String captchaCode = "3991"; given(captchaManager.checkCode(name, captchaCode)).willReturn(true); - MessageTask messageTask = mock(MessageTask.class); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.getMessageTask()).willReturn(messageTask); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); // when command.executeCommand(player, Collections.singletonList(captchaCode)); @@ -96,7 +90,7 @@ public class CaptchaCommandTest { verifyNoMoreInteractions(captchaManager); verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS); verify(commandService).send(player, MessageKey.LOGIN_MESSAGE); - verify(messageTask).setMuted(false); + verify(limboPlayerTaskManager).unmuteMessageTask(player); verifyNoMoreInteractions(commandService); } diff --git a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java index d586b425a..fac4917a9 100644 --- a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java +++ b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java @@ -3,15 +3,15 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupHandler; import fr.xephi.authme.permission.AuthGroupType; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -53,7 +53,7 @@ public class AsynchronousUnregisterTest { @Mock private BukkitService bukkitService; @Mock - private LimboCache limboCache; + private LimboService limboService; @Mock private LimboPlayerTaskManager limboPlayerTaskManager; @Mock @@ -85,7 +85,7 @@ public class AsynchronousUnregisterTest { // then verify(service).send(player, MessageKey.WRONG_PASSWORD); verify(passwordSecurity).comparePassword(userPassword, password, name); - verifyZeroInteractions(dataSource, limboPlayerTaskManager, limboCache, authGroupHandler, teleportationService); + verifyZeroInteractions(dataSource, limboPlayerTaskManager, limboService, authGroupHandler, teleportationService); verify(player, only()).getName(); } diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java index 51110d91e..5335da299 100644 --- a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java @@ -2,8 +2,8 @@ package fr.xephi.authme.task; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.service.BukkitService; @@ -47,7 +47,7 @@ public class LimboPlayerTaskManagerTest { private BukkitService bukkitService; @Mock - private LimboCache limboCache; + private LimboService limboService; @Mock private PlayerCache playerCache; @@ -62,7 +62,7 @@ public class LimboPlayerTaskManagerTest { // given String name = "bobby"; LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); MessageKey key = MessageKey.REGISTER_MESSAGE; given(messages.retrieve(key)).willReturn(new String[]{"Please register!"}); int interval = 12; @@ -82,14 +82,14 @@ public class LimboPlayerTaskManagerTest { public void shouldNotScheduleTaskForMissingLimboPlayer() { // given String name = "ghost"; - given(limboCache.getPlayerData(name)).willReturn(null); + given(limboService.getLimboPlayer(name)).willReturn(null); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(5); // when limboPlayerTaskManager.registerMessageTask(name, true); // then - verify(limboCache).getPlayerData(name); + verify(limboService).getLimboPlayer(name); verifyZeroInteractions(bukkitService); verifyZeroInteractions(messages); } @@ -117,7 +117,7 @@ public class LimboPlayerTaskManagerTest { given(limboPlayer.getMessageTask()).willReturn(existingMessageTask); String name = "bobby"; - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); // when @@ -136,7 +136,7 @@ public class LimboPlayerTaskManagerTest { Player player = mock(Player.class); given(player.getName()).willReturn(name); LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(30); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); @@ -156,7 +156,7 @@ public class LimboPlayerTaskManagerTest { String name = "Phantom_"; Player player = mock(Player.class); given(player.getName()).willReturn(name); - given(limboCache.getPlayerData(name)).willReturn(null); + given(limboService.getLimboPlayer(name)).willReturn(null); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(27); // when @@ -189,7 +189,7 @@ public class LimboPlayerTaskManagerTest { LimboPlayer limboPlayer = mock(LimboPlayer.class); BukkitTask existingTask = mock(BukkitTask.class); given(limboPlayer.getTimeoutTask()).willReturn(existingTask); - given(limboCache.getPlayerData(name)).willReturn(limboPlayer); + given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(18); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); From 021497b9e6bb497278f38ba8d49ac81ba1c94e86 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 21:47:46 +0100 Subject: [PATCH 062/125] #1113 Handle LimboPlayers within LimboService (remove LimboCache) (work in progress) - Delete LimboCache and LimboPlayerStorage: LimboService now handles all LimboPlayer actions - Revoke player rights when creating a LimboPlayer, within the LimboService - Various fixes and improvements --- .../authme/RegisterAdminCommand.java | 6 - .../xephi/authme/data/limbo/LimboCache.java | 151 ------------ .../xephi/authme/data/limbo/LimboPlayer.java | 10 +- .../authme/data/limbo/LimboPlayerStorage.java | 213 ----------------- .../xephi/authme/data/limbo/LimboService.java | 104 ++++++++- .../authme/process/join/AsynchronousJoin.java | 23 +- .../process/login/ProcessSyncPlayerLogin.java | 5 +- .../process/logout/AsynchronousLogout.java | 8 +- .../ProcessSynchronousPlayerLogout.java | 17 +- .../unregister/AsynchronousUnregister.java | 20 +- .../authme/task/LimboPlayerTaskManager.java | 31 +-- .../authme/RegisterAdminCommandTest.java | 4 - .../authme/data/limbo/LimboCacheTest.java | 215 ------------------ .../data/limbo/LimboPlayerStorageTest.java | 150 ------------ .../AsynchronousUnregisterTest.java | 13 +- .../task/LimboPlayerTaskManagerTest.java | 18 +- 16 files changed, 138 insertions(+), 850 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/data/limbo/LimboCache.java delete mode 100644 src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java delete mode 100644 src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java delete mode 100644 src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index d4ad49466..2f1341b92 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -3,7 +3,6 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -38,9 +37,6 @@ public class RegisterAdminCommand implements ExecutableCommand { @Inject private ValidationService validationService; - @Inject - private LimboService limboService; - @Override public void executeCommand(final CommandSender sender, List arguments) { // Get the player name and password @@ -83,8 +79,6 @@ public class RegisterAdminCommand implements ExecutableCommand { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { @Override public void run() { - // TODO #1113: Is it necessary to restore here or is this covered by the quit process? - limboService.restoreData(player); player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER)); } }); diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java deleted file mode 100644 index 99416636f..000000000 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java +++ /dev/null @@ -1,151 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import javax.inject.Inject; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Manages all {@link LimboPlayer} instances. - */ -class LimboCache { - - private final Map cache = new ConcurrentHashMap<>(); - - private LimboPlayerStorage limboPlayerStorage; - private PermissionsManager permissionsManager; - private SpawnLoader spawnLoader; - - @Inject - LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) { - this.permissionsManager = permissionsManager; - this.spawnLoader = spawnLoader; - this.limboPlayerStorage = limboPlayerStorage; - } - - /** - * Load player data if exist, otherwise current player's data will be stored. - * - * @param player Player instance to add. - */ - public void addPlayerData(Player player) { - String name = player.getName().toLowerCase(); - Location location = spawnLoader.getPlayerLocationOrSpawn(player); - boolean operator = player.isOp(); - boolean flyEnabled = player.getAllowFlight(); - float walkSpeed = player.getWalkSpeed(); - float flySpeed = player.getFlySpeed(); - String playerGroup = ""; - 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); - if (cache != null) { - location = cache.getLocation(); - playerGroup = cache.getGroup(); - operator = cache.isOperator(); - flyEnabled = cache.isCanFly(); - walkSpeed = cache.getWalkSpeed(); - flySpeed = cache.getFlySpeed(); - } - } else { - limboPlayerStorage.saveData(player); - } - - cache.put(name, new LimboPlayer(location, operator, playerGroup, flyEnabled, walkSpeed, flySpeed)); - } - - /** - * Restore player's data to player if exist. - * - * @param player Player instance to restore - */ - public void restoreData(Player player) { - String lowerName = player.getName().toLowerCase(); - if (cache.containsKey(lowerName)) { - LimboPlayer data = cache.get(lowerName); - player.setOp(data.isOperator()); - player.setAllowFlight(data.isCanFly()); - float walkSpeed = data.getWalkSpeed(); - float flySpeed = data.getFlySpeed(); - // Reset the speed value if it was 0 - if (walkSpeed < 0.01f) { - walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - } - if (flySpeed < 0.01f) { - flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - } - player.setWalkSpeed(walkSpeed); - player.setFlySpeed(flySpeed); - data.clearTasks(); - } - } - - /** - * Remove PlayerData from cache and disk. - * - * @param player Player player to remove. - */ - public void deletePlayerData(Player player) { - removeFromCache(player); - limboPlayerStorage.removeData(player); - } - - /** - * Remove PlayerData from cache. - * - * @param player player to remove. - */ - public void removeFromCache(Player player) { - String name = player.getName().toLowerCase(); - LimboPlayer cachedPlayer = cache.remove(name); - if (cachedPlayer != null) { - cachedPlayer.clearTasks(); - } - } - - /** - * Method getPlayerData. - * - * @param name String - * - * @return PlayerData - */ - public LimboPlayer getPlayerData(String name) { - checkNotNull(name); - return cache.get(name.toLowerCase()); - } - - /** - * Method hasPlayerData. - * - * @param name String - * - * @return boolean - */ - public boolean hasPlayerData(String name) { - checkNotNull(name); - return cache.containsKey(name.toLowerCase()); - } - - /** - * Method updatePlayerData. - * - * @param player Player - */ - public void updatePlayerData(Player player) { - checkNotNull(player); - removeFromCache(player); - addPlayerData(player); - } -} 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 45fcd7ad8..6ba4ae2c5 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java @@ -118,13 +118,7 @@ public class LimboPlayer { * Clears all tasks associated to the player. */ public void clearTasks() { - if (messageTask != null) { - messageTask.cancel(); - } - messageTask = null; - if (timeoutTask != null) { - timeoutTask.cancel(); - } - timeoutTask = null; + setMessageTask(null); + setTimeoutTask(null); } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java deleted file mode 100644 index 4ab45a8d3..000000000 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java +++ /dev/null @@ -1,213 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import com.google.common.io.Files; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.initialization.DataFolder; -import fr.xephi.authme.permission.PermissionsManager; -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; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; - -/** - * Class used to store player's data (OP, flying, speed, position) to disk. - */ -class LimboPlayerStorage { - - private final Gson gson; - private final File cacheDir; - private PermissionsManager permissionsManager; - private SpawnLoader spawnLoader; - private BukkitService bukkitService; - - @Inject - LimboPlayerStorage(@DataFolder File dataFolder, PermissionsManager permsMan, - SpawnLoader spawnLoader, BukkitService bukkitService) { - this.permissionsManager = permsMan; - this.spawnLoader = spawnLoader; - this.bukkitService = bukkitService; - - cacheDir = new File(dataFolder, "playerdata"); - if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) { - ConsoleLogger.warning("Failed to create userdata directory."); - } - gson = new GsonBuilder() - .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) - .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer()) - .setPrettyPrinting() - .create(); - } - - /** - * Read and construct new PlayerData from existing player data. - * - * @param player player to read - * - * @return PlayerData object if the data is exist, null otherwise. - */ - public LimboPlayer readData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id + File.separator + "data.json"); - if (!file.exists()) { - return null; - } - - try { - String str = Files.toString(file, StandardCharsets.UTF_8); - return gson.fromJson(str, LimboPlayer.class); - } catch (IOException e) { - ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e); - return null; - } - } - - /** - * Save player data (OP, flying, location, etc) to disk. - * - * @param player player to save - */ - public void saveData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - Location location = spawnLoader.getPlayerLocationOrSpawn(player); - String group = ""; - if (permissionsManager.hasGroupSupport()) { - group = permissionsManager.getPrimaryGroup(player); - } - boolean operator = player.isOp(); - boolean canFly = player.getAllowFlight(); - float walkSpeed = player.getWalkSpeed(); - float flySpeed = player.getFlySpeed(); - LimboPlayer limboPlayer = new LimboPlayer(location, operator, group, canFly, walkSpeed, flySpeed); - try { - File file = new File(cacheDir, id + File.separator + "data.json"); - Files.createParentDirs(file); - Files.touch(file); - Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8); - } catch (IOException e) { - ConsoleLogger.logException("Failed to write " + player.getName() + " data.", e); - } - } - - /** - * Remove player data, this will delete - * "playerdata/<uuid or name>/" folder from disk. - * - * @param player player to remove - */ - public void removeData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id); - if (file.exists()) { - FileUtils.purgeDirectory(file); - if (!file.delete()) { - ConsoleLogger.warning("Failed to remove " + player.getName() + " cache."); - } - } - } - - /** - * Use to check is player data is exist. - * - * @param player player to check - * - * @return true if data exist, false otherwise. - */ - public boolean hasData(Player player) { - String id = PlayerUtils.getUUIDorName(player); - File file = new File(cacheDir, id + File.separator + "data.json"); - return file.exists(); - } - - private class LimboPlayerDeserializer implements JsonDeserializer { - @Override - public LimboPlayer deserialize(JsonElement jsonElement, Type type, - JsonDeserializationContext context) { - JsonObject jsonObject = jsonElement.getAsJsonObject(); - if (jsonObject == null) { - return null; - } - - Location loc = null; - String group = ""; - boolean operator = false; - boolean canFly = false; - float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - - JsonElement e; - if ((e = jsonObject.getAsJsonObject("location")) != null) { - JsonObject obj = e.getAsJsonObject(); - World world = bukkitService.getWorld(obj.get("world").getAsString()); - if (world != null) { - double x = obj.get("x").getAsDouble(); - double y = obj.get("y").getAsDouble(); - double z = obj.get("z").getAsDouble(); - float yaw = obj.get("yaw").getAsFloat(); - float pitch = obj.get("pitch").getAsFloat(); - loc = new Location(world, x, y, z, yaw, pitch); - } - } - if ((e = jsonObject.get("group")) != null) { - group = e.getAsString(); - } - if ((e = jsonObject.get("operator")) != null) { - operator = e.getAsBoolean(); - } - if ((e = jsonObject.get("can-fly")) != null) { - canFly = e.getAsBoolean(); - } - if ((e = jsonObject.get("walk-speed")) != null) { - walkSpeed = e.getAsFloat(); - } - if ((e = jsonObject.get("fly-speed")) != null) { - flySpeed = e.getAsFloat(); - } - - return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); - } - } - - private class LimboPlayerSerializer implements JsonSerializer { - @Override - public JsonElement serialize(LimboPlayer limboPlayer, Type type, - JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - obj.addProperty("group", limboPlayer.getGroup()); - - Location loc = limboPlayer.getLocation(); - JsonObject obj2 = new JsonObject(); - obj2.addProperty("world", loc.getWorld().getName()); - obj2.addProperty("x", loc.getX()); - obj2.addProperty("y", loc.getY()); - obj2.addProperty("z", loc.getZ()); - obj2.addProperty("yaw", loc.getYaw()); - obj2.addProperty("pitch", loc.getPitch()); - obj.add("location", obj2); - - obj.addProperty("operator", limboPlayer.isOperator()); - obj.addProperty("can-fly", limboPlayer.isCanFly()); - obj.addProperty("walk-speed", limboPlayer.getWalkSpeed()); - obj.addProperty("fly-speed", limboPlayer.getFlySpeed()); - return obj; - } - } - - -} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index 0c3b2532d..0333a1651 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -1,8 +1,16 @@ 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.RestrictionSettings; +import org.bukkit.Location; import org.bukkit.entity.Player; import javax.inject.Inject; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Service for managing players that are in "limbo," a temporary state players are @@ -10,8 +18,16 @@ import javax.inject.Inject; */ public class LimboService { + private Map entries = new ConcurrentHashMap<>(); + @Inject - private LimboCache limboCache; + private SpawnLoader spawnLoader; + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private Settings settings; LimboService() { } @@ -19,13 +35,34 @@ public class LimboService { /** * Restores the limbo data and subsequently deletes the entry. + *

    + * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and + * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. * * @param player the player whose data should be restored */ public void restoreData(Player player) { - // TODO #1113: Think about architecture for various "restore" strategies - limboCache.restoreData(player); - limboCache.deletePlayerData(player); + String lowerName = player.getName().toLowerCase(); + LimboPlayer limbo = entries.remove(lowerName); + + if (limbo == null) { + ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName); + } else { + player.setOp(limbo.isOperator()); + player.setAllowFlight(limbo.isCanFly()); + float walkSpeed = limbo.getWalkSpeed(); + float flySpeed = limbo.getFlySpeed(); + // Reset the speed value if it was 0 + if (walkSpeed < 0.01f) { + walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; + } + if (flySpeed < 0.01f) { + flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; + } + player.setWalkSpeed(walkSpeed); + player.setFlySpeed(flySpeed); + limbo.clearTasks(); + } } /** @@ -35,7 +72,7 @@ public class LimboService { * @return the associated limbo player, or null if none available */ public LimboPlayer getLimboPlayer(String name) { - return limboCache.getPlayerData(name); + return entries.get(name.toLowerCase()); } /** @@ -45,16 +82,65 @@ public class LimboService { * @return true if present, false otherwise */ public boolean hasLimboPlayer(String name) { - return limboCache.hasPlayerData(name); + return entries.containsKey(name.toLowerCase()); } /** - * Creates a LimboPlayer for the given player. + * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player. * * @param player the player to process */ public void createLimboPlayer(Player player) { - // TODO #1113: We should remove the player's data in here as well - limboCache.addPlayerData(player); + final String name = player.getName().toLowerCase(); + + LimboPlayer existingLimbo = entries.remove(name); + if (existingLimbo != null) { + existingLimbo.clearTasks(); + ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name); + } + + LimboPlayer limboPlayer = newLimboPlayer(player); + revokeLimboStates(player); + entries.put(name, limboPlayer); + } + + /** + * Creates a LimboPlayer with the given player's details. + * + * @param player the player to process + * @return limbo player with the player's data + */ + private LimboPlayer newLimboPlayer(Player player) { + Location location = spawnLoader.getPlayerLocationOrSpawn(player); + boolean isOperator = player.isOp(); + boolean flyEnabled = player.getAllowFlight(); + float walkSpeed = player.getWalkSpeed(); + float flySpeed = player.getFlySpeed(); + String playerGroup = ""; + if (permissionsManager.hasGroupSupport()) { + playerGroup = permissionsManager.getPrimaryGroup(player); + } + ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); + + return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed); + } + + /** + * Removes the data that is saved in a LimboPlayer from the player. + *

    + * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and + * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. + * + * @param player the player to set defaults to + */ + private void revokeLimboStates(Player player) { + player.setOp(false); + player.setAllowFlight(false); + + if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) + && settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { + player.setFlySpeed(0.0f); + player.setWalkSpeed(0.0f); + } } } 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 b03a70f8f..af55ec654 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -111,7 +111,6 @@ public class AsynchronousJoin implements AsynchronousProcess { final boolean isAuthAvailable = database.isAuthAvailable(name); if (isAuthAvailable) { - limboService.createLimboPlayer(player); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); // Protect inventory @@ -141,11 +140,6 @@ public class AsynchronousJoin implements AsynchronousProcess { } } } else { - // TODO #1113: Why delete and add LimboPlayer again? - // Not Registered. Delete old data, load default one. - // limboCache.deletePlayerData(player); - // limboCache.addPlayerData(player); - // Groups logic service.setGroup(player, AuthGroupType.UNREGISTERED); @@ -158,12 +152,11 @@ public class AsynchronousJoin implements AsynchronousProcess { final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { - player.setOp(false); - if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { - player.setFlySpeed(0.0f); - player.setWalkSpeed(0.0f); - } + // TODO #1113: Find an elegant way to deop unregistered players (and disable fly status etc.?) + limboService.createLimboPlayer(player); + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); + player.setNoDamageTicks(registrationTimeout); if (pluginHookService.isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) { player.performCommand("motd"); @@ -175,10 +168,6 @@ public class AsynchronousJoin implements AsynchronousProcess { } commandManager.runCommandsOnJoin(player); }); - - // Timeout and message task - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); } /** @@ -189,7 +178,7 @@ public class AsynchronousJoin implements AsynchronousProcess { * @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 + * 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)) { 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 4cdef9949..206a55fa5 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -65,15 +65,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { public void processPlayerLogin(Player player) { final String name = player.getName().toLowerCase(); + commonService.setGroup(player, AuthGroupType.LOGGED_IN); final LimboPlayer limbo = limboService.getLimboPlayer(name); // Limbo contains the State of the Player before /login if (limbo != null) { limboService.restoreData(player); - // do we really need to use location from database for now? - // because LimboCache#restoreData teleport player to last location. } - // TODO #1113: Need to set group before deleting limboPlayer?? - commonService.setGroup(player, AuthGroupType.LOGGED_IN); if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { restoreInventory(player); diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java index 1120fd595..1f59d9657 100644 --- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java @@ -2,12 +2,11 @@ package fr.xephi.authme.process.logout; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.AsynchronousProcess; -import fr.xephi.authme.service.CommonService; import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; @@ -24,9 +23,6 @@ public class AsynchronousLogout implements AsynchronousProcess { @Inject private PlayerCache playerCache; - @Inject - private LimboService limboService; - @Inject private SyncProcessManager syncProcessManager; @@ -47,8 +43,6 @@ public class AsynchronousLogout implements AsynchronousProcess { database.updateQuitLoc(auth); } - // TODO #1113: Would we not have to RESTORE the limbo player here? - limboService.createLimboPlayer(player); playerCache.removePlayer(name); database.setUnlogged(name); syncProcessManager.processSyncPlayerLogout(player); 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 9f5dc32d3..ffafe16b7 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -2,6 +2,7 @@ package fr.xephi.authme.process.logout; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.SessionManager; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.events.LogoutEvent; import fr.xephi.authme.listener.protocollib.ProtocolLibService; import fr.xephi.authme.message.MessageKey; @@ -36,6 +37,9 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { @Inject private LimboPlayerTaskManager limboPlayerTaskManager; + @Inject + private LimboService limboService; + @Inject private SessionManager sessionManager; @@ -53,11 +57,11 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { protocolLibService.sendBlankInventoryPacket(player); } + applyLogoutEffect(player); + limboPlayerTaskManager.registerTimeoutTask(player); limboPlayerTaskManager.registerMessageTask(name, true); - applyLogoutEffect(player); - // Player is now logout... Time to fire event ! bukkitService.callEvent(new LogoutEvent(player)); @@ -77,15 +81,8 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { } // Set player's data to unauthenticated + limboService.createLimboPlayer(player); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); - player.setOp(false); - player.setAllowFlight(false); - // Remove speed - if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && service.getProperty(RestrictionSettings.REMOVE_SPEED)) { - player.setFlySpeed(0.0f); - player.setWalkSpeed(0.0f); - } } } diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 879edd181..667901560 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -111,12 +111,13 @@ public class AsynchronousUnregister implements AsynchronousProcess { teleportationService.teleportOnJoin(player); player.saveData(); - // TODO #1113: Why delete? limboCache.deletePlayerData(player); - limboService.createLimboPlayer(player); + bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { + limboService.createLimboPlayer(player); + limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerMessageTask(name, false); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, false); - applyBlindEffect(player); + applyBlindEffect(player); + }); } authGroupHandler.setGroup(player, AuthGroupType.UNREGISTERED); service.send(player, MessageKey.UNREGISTERED_SUCCESS); @@ -124,13 +125,8 @@ public class AsynchronousUnregister implements AsynchronousProcess { private void applyBlindEffect(final Player player) { if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { - final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - bukkitService.runTask(new Runnable() { - @Override - public void run() { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); - } - }); + int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2)); } } } diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java index 25e118634..f66f9445d 100644 --- a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java +++ b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java @@ -11,7 +11,6 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import javax.inject.Inject; @@ -19,7 +18,7 @@ import javax.inject.Inject; import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; /** - * Registers tasks associated with a PlayerData. + * Registers tasks associated with a LimboPlayer. */ public class LimboPlayerTaskManager { @@ -55,9 +54,8 @@ public class LimboPlayerTaskManager { if (interval > 0) { final LimboPlayer limboPlayer = limboService.getLimboPlayer(name); if (limboPlayer == null) { - ConsoleLogger.info("PlayerData for '" + name + "' is not available"); + ConsoleLogger.info("LimboPlayer for '" + name + "' is not available (MessageTask)"); } else { - cancelTask(limboPlayer.getMessageTask()); MessageTask messageTask = new MessageTask(name, messages.retrieve(key), bukkitService, playerCache); bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND); limboPlayer.setMessageTask(messageTask); @@ -75,9 +73,8 @@ public class LimboPlayerTaskManager { if (timeout > 0) { final LimboPlayer limboPlayer = limboService.getLimboPlayer(player.getName()); if (limboPlayer == null) { - ConsoleLogger.info("PlayerData for '" + player.getName() + "' is not available"); + ConsoleLogger.info("LimboPlayer for '" + player.getName() + "' is not available (TimeoutTask)"); } else { - cancelTask(limboPlayer.getTimeoutTask()); String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout); limboPlayer.setTimeoutTask(task); @@ -120,28 +117,6 @@ public class LimboPlayerTaskManager { } } - /** - * Null-safe method to cancel a potentially existing task. - * - * @param task the task to cancel (or null) - */ - private static void cancelTask(BukkitTask task) { - if (task != null) { - task.cancel(); - } - } - - /** - * Null-safe method to cancel a potentially existing task. - * - * @param task the task to cancel (or null) - */ - private static void cancelTask(BukkitRunnable task) { - if (task != null) { - task.cancel(); - } - } - /** * Null-safe method to set the muted flag on a message task. * diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java index ebe50a745..5ee2161e1 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommandTest.java @@ -2,7 +2,6 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.security.PasswordSecurity; @@ -56,9 +55,6 @@ public class RegisterAdminCommandTest { @Mock private ValidationService validationService; - @Mock - private LimboService limboService; - @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java deleted file mode 100644 index 8d307c25d..000000000 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboCacheTest.java +++ /dev/null @@ -1,215 +0,0 @@ -package fr.xephi.authme.data.limbo; - -import fr.xephi.authme.ReflectionTestUtils; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.settings.SpawnLoader; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -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.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Test for {@link LimboCache}. - */ -@RunWith(MockitoJUnitRunner.class) -public class LimboCacheTest { - - @InjectMocks - private LimboCache limboCache; - - @Mock - private PermissionsManager permissionsManager; - - @Mock - private SpawnLoader spawnLoader; - - @Mock - private LimboPlayerStorage limboPlayerStorage; - - @Test - public void shouldAddPlayerData() { - // given - Player player = mock(Player.class); - String name = "Bobby"; - given(player.getName()).willReturn(name); - Location location = mock(Location.class); - given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(location); - given(player.isOp()).willReturn(true); - float walkSpeed = 2.1f; - given(player.getWalkSpeed()).willReturn(walkSpeed); - given(player.getAllowFlight()).willReturn(true); - float flySpeed = 3.0f; - given(player.getFlySpeed()).willReturn(flySpeed); - given(permissionsManager.hasGroupSupport()).willReturn(true); - String group = "test-group"; - given(permissionsManager.getPrimaryGroup(player)).willReturn(group); - given(limboPlayerStorage.hasData(player)).willReturn(false); - - // when - limboCache.addPlayerData(player); - - // then - LimboPlayer limboPlayer = limboCache.getPlayerData(name); - assertThat(limboPlayer.getLocation(), equalTo(location)); - assertThat(limboPlayer.isOperator(), equalTo(true)); - assertThat(limboPlayer.getWalkSpeed(), equalTo(walkSpeed)); - assertThat(limboPlayer.isCanFly(), equalTo(true)); - assertThat(limboPlayer.getFlySpeed(), equalTo(flySpeed)); - assertThat(limboPlayer.getGroup(), equalTo(group)); - } - - @Test - public void shouldGetPlayerDataFromDisk() { - // given - String name = "player01"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - given(limboPlayerStorage.hasData(player)).willReturn(true); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayerStorage.readData(player)).willReturn(limboPlayer); - float walkSpeed = 2.4f; - given(limboPlayer.getWalkSpeed()).willReturn(walkSpeed); - given(limboPlayer.isCanFly()).willReturn(true); - float flySpeed = 1.0f; - given(limboPlayer.getFlySpeed()).willReturn(flySpeed); - String group = "primary-group"; - given(limboPlayer.getGroup()).willReturn(group); - - // when - limboCache.addPlayerData(player); - - // then - LimboPlayer result = limboCache.getPlayerData(name); - assertThat(result.getWalkSpeed(), equalTo(walkSpeed)); - assertThat(result.isCanFly(), equalTo(true)); - assertThat(result.getFlySpeed(), equalTo(flySpeed)); - assertThat(result.getGroup(), equalTo(group)); - } - - @Test - public void shouldRestorePlayerInfo() { - // given - String name = "Champ"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.isOperator()).willReturn(true); - float walkSpeed = 2.4f; - given(limboPlayer.getWalkSpeed()).willReturn(walkSpeed); - given(limboPlayer.isCanFly()).willReturn(true); - float flySpeed = 1.0f; - given(limboPlayer.getFlySpeed()).willReturn(flySpeed); - getCache().put(name.toLowerCase(), limboPlayer); - - // when - limboCache.restoreData(player); - - // then - verify(player).setOp(true); - verify(player).setWalkSpeed(walkSpeed); - verify(player).setAllowFlight(true); - verify(player).setFlySpeed(flySpeed); - verify(limboPlayer).clearTasks(); - } - - @Test - public void shouldResetPlayerSpeed() { - // given - String name = "Champ"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboPlayer.isOperator()).willReturn(true); - given(limboPlayer.getWalkSpeed()).willReturn(0f); - given(limboPlayer.isCanFly()).willReturn(true); - given(limboPlayer.getFlySpeed()).willReturn(0f); - getCache().put(name.toLowerCase(), limboPlayer); - - // when - limboCache.restoreData(player); - - // then - verify(player).setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED); - verify(player).setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); - } - - @Test - public void shouldNotInteractWithPlayerIfNoDataAvailable() { - // given - String name = "player"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.restoreData(player); - - // then - verify(player).getName(); - verifyNoMoreInteractions(player); - } - - @Test - public void shouldRemoveAndClearTasks() { - // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); - String name = "abcdef"; - getCache().put(name, limboPlayer); - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.removeFromCache(player); - - // then - assertThat(getCache(), anEmptyMap()); - verify(limboPlayer).clearTasks(); - } - - @Test - public void shouldDeleteFromCacheAndStorage() { - // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); - String name = "SomeName"; - getCache().put(name.toLowerCase(), limboPlayer); - getCache().put("othername", mock(LimboPlayer.class)); - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - - // when - limboCache.deletePlayerData(player); - - // then - assertThat(getCache(), aMapWithSize(1)); - verify(limboPlayer).clearTasks(); - verify(limboPlayerStorage).removeData(player); - } - - @Test - public void shouldReturnIfHasData() { - // given - String name = "tester"; - getCache().put(name, mock(LimboPlayer.class)); - - // when / then - assertThat(limboCache.hasPlayerData(name), equalTo(true)); - assertThat(limboCache.hasPlayerData("someone_else"), equalTo(false)); - } - - private Map getCache() { - return ReflectionTestUtils.getFieldValue(LimboCache.class, limboCache, "cache"); - } -} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java deleted file mode 100644 index 6d3468543..000000000 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerStorageTest.java +++ /dev/null @@ -1,150 +0,0 @@ -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.initialization.DataFolder; -import fr.xephi.authme.permission.PermissionsManager; -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; -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.UUID; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Test for {@link LimboPlayerStorage}. - */ -@RunWith(DelayedInjectionRunner.class) -public class LimboPlayerStorageTest { - - private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PlayerDataStorageTest".getBytes()); - private static final String SOURCE_FOLDER = TestHelper.PROJECT_ROOT + "data/backup/"; - - @InjectDelayed - private LimboPlayerStorage limboPlayerStorage; - - @Mock - private SpawnLoader spawnLoader; - - @Mock - private BukkitService bukkitService; - - @Mock - private PermissionsManager permissionsManager; - - @DataFolder - private File dataFolder; - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @BeforeInjecting - public void copyTestFiles() throws IOException { - dataFolder = temporaryFolder.newFolder(); - File playerFolder = new File(dataFolder, FileUtils.makePath("playerdata", SAMPLE_UUID.toString())); - if (!playerFolder.mkdirs()) { - throw new IllegalStateException("Cannot create '" + playerFolder.getAbsolutePath() + "'"); - } - Files.copy(TestHelper.getJarPath(FileUtils.makePath(SOURCE_FOLDER, "sample-folder", "data.json")), - new File(playerFolder, "data.json").toPath()); - } - - @Test - public void shouldReadDataFromFile() { - // given - Player player = mock(Player.class); - given(player.getUniqueId()).willReturn(SAMPLE_UUID); - World world = mock(World.class); - given(bukkitService.getWorld("nether")).willReturn(world); - - // when - LimboPlayer data = limboPlayerStorage.readData(player); - - // then - assertThat(data, not(nullValue())); - assertThat(data.isOperator(), equalTo(true)); - assertThat(data.isCanFly(), equalTo(true)); - assertThat(data.getWalkSpeed(), equalTo(0.2f)); - assertThat(data.getFlySpeed(), equalTo(0.1f)); - assertThat(data.getGroup(), equalTo("players")); - Location location = data.getLocation(); - assertThat(location.getX(), equalTo(-113.219)); - assertThat(location.getY(), equalTo(72.0)); - assertThat(location.getZ(), equalTo(130.637)); - assertThat(location.getWorld(), equalTo(world)); - assertThat(location.getPitch(), equalTo(24.15f)); - assertThat(location.getYaw(), equalTo(-292.484f)); - } - - @Test - public void shouldReturnNullForUnavailablePlayer() { - // given - Player player = mock(Player.class); - given(player.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("other-player".getBytes())); - - // when - LimboPlayer data = limboPlayerStorage.readData(player); - - // then - assertThat(data, nullValue()); - } - - @Test - public void shouldReturnIfHasData() { - // given - Player player1 = mock(Player.class); - given(player1.getUniqueId()).willReturn(SAMPLE_UUID); - Player player2 = mock(Player.class); - given(player2.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("not-stored".getBytes())); - - // when / then - assertThat(limboPlayerStorage.hasData(player1), equalTo(true)); - assertThat(limboPlayerStorage.hasData(player2), equalTo(false)); - } - - @Test - public void shouldSavePlayerData() { - // given - Player player = mock(Player.class); - UUID uuid = UUID.nameUUIDFromBytes("New player".getBytes()); - given(player.getUniqueId()).willReturn(uuid); - given(permissionsManager.getPrimaryGroup(player)).willReturn("primary-grp"); - given(player.isOp()).willReturn(true); - given(player.getWalkSpeed()).willReturn(1.2f); - given(player.getFlySpeed()).willReturn(0.8f); - given(player.getAllowFlight()).willReturn(true); - - World world = mock(World.class); - given(world.getName()).willReturn("player-world"); - Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f); - given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(location); - - // when - limboPlayerStorage.saveData(player); - - // then - File playerFile = new File(dataFolder, FileUtils.makePath("playerdata", uuid.toString(), "data.json")); - assertThat(playerFile.exists(), equalTo(true)); - // TODO ljacqu 20160711: Check contents of file - } - -} diff --git a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java index fac4917a9..195de52c8 100644 --- a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java +++ b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java @@ -14,7 +14,6 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -104,8 +103,6 @@ public class AsynchronousUnregisterTest { given(passwordSecurity.comparePassword(userPassword, password, name)).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(12); // when asynchronousUnregister.unregister(player, userPassword); @@ -117,7 +114,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test @@ -135,8 +132,6 @@ public class AsynchronousUnregisterTest { given(passwordSecurity.comparePassword(userPassword, password, name)).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(0); // when asynchronousUnregister.unregister(player, userPassword); @@ -148,7 +143,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test @@ -237,8 +232,6 @@ public class AsynchronousUnregisterTest { given(player.isOnline()).willReturn(true); given(dataSource.removeAuth(name)).willReturn(true); given(service.getProperty(RegistrationSettings.FORCE)).willReturn(true); - given(service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)).willReturn(true); - given(service.getProperty(RestrictionSettings.TIMEOUT)).willReturn(12); CommandSender initiator = mock(CommandSender.class); // when @@ -251,7 +244,7 @@ public class AsynchronousUnregisterTest { verify(playerCache).removePlayer(name); verify(teleportationService).teleportOnJoin(player); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verify(bukkitService).runTask(any(Runnable.class)); + verify(bukkitService).scheduleSyncTaskFromOptionallyAsyncTask(any(Runnable.class)); } @Test diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java index 5335da299..0d3d8f715 100644 --- a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java @@ -20,6 +20,11 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -112,9 +117,9 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldCancelExistingMessageTask() { // given - LimboPlayer limboPlayer = mock(LimboPlayer.class); + LimboPlayer limboPlayer = new LimboPlayer(null, true, "grp", false, 0.1f, 0.0f); MessageTask existingMessageTask = mock(MessageTask.class); - given(limboPlayer.getMessageTask()).willReturn(existingMessageTask); + limboPlayer.setMessageTask(existingMessageTask); String name = "bobby"; given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); @@ -124,7 +129,8 @@ public class LimboPlayerTaskManagerTest { limboPlayerTaskManager.registerMessageTask(name, false); // then - verify(limboPlayer).setMessageTask(any(MessageTask.class)); + assertThat(limboPlayer.getMessageTask(), not(nullValue())); + assertThat(limboPlayer.getMessageTask(), not(sameInstance(existingMessageTask))); verify(messages).retrieve(MessageKey.REGISTER_MESSAGE); verify(existingMessageTask).cancel(); } @@ -186,9 +192,9 @@ public class LimboPlayerTaskManagerTest { String name = "l33tPlayer"; Player player = mock(Player.class); given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = mock(LimboPlayer.class); + LimboPlayer limboPlayer = new LimboPlayer(null, false, "", true, 0.3f, 0.1f); BukkitTask existingTask = mock(BukkitTask.class); - given(limboPlayer.getTimeoutTask()).willReturn(existingTask); + limboPlayer.setTimeoutTask(existingTask); given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(18); BukkitTask bukkitTask = mock(BukkitTask.class); @@ -199,7 +205,7 @@ public class LimboPlayerTaskManagerTest { // then verify(existingTask).cancel(); - verify(limboPlayer).setTimeoutTask(bukkitTask); + assertThat(limboPlayer.getTimeoutTask(), equalTo(bukkitTask)); verify(bukkitService).runTaskLater(any(TimeoutTask.class), eq(360L)); // 18 * TICKS_PER_SECOND verify(messages).retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); } From 648e71cf0f3c1c8cf6f6087a7127caf2fdd50488 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 6 Mar 2017 06:11:59 +0200 Subject: [PATCH 063/125] 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 f62b00e15..3c007282a 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 f512d67b7..e82aadfa7 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 064/125] 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 3c007282a..9dc842535 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 05e2e3d90..e752057c1 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 7d4bfcd99d54f858974174e91fd53dfa89007a9f Mon Sep 17 00:00:00 2001 From: EbonJaguar Date: Mon, 6 Mar 2017 13:54:46 -0500 Subject: [PATCH 065/125] - Introduce /email code - Add max tries for /email code - Introduce a PasswordRecoveryService --- docs/config.md | 8 +- .../executable/email/ProcessCodeCommand.java | 57 ++++++++ .../executable/email/RecoverEmailCommand.java | 98 +------------ .../fr/xephi/authme/message/MessageKey.java | 7 +- .../service/PasswordRecoveryService.java | 125 ++++++++++++++++ .../authme/service/RecoveryCodeService.java | 35 ++++- .../settings/properties/SecuritySettings.java | 4 + .../authme/util/expiring/TimedCounter.java | 15 ++ src/main/resources/messages/messages_bg.yml | 3 +- src/main/resources/messages/messages_br.yml | 2 + src/main/resources/messages/messages_cz.yml | 2 + src/main/resources/messages/messages_de.yml | 2 + src/main/resources/messages/messages_en.yml | 3 +- src/main/resources/messages/messages_es.yml | 2 + src/main/resources/messages/messages_eu.yml | 3 +- src/main/resources/messages/messages_fi.yml | 3 +- src/main/resources/messages/messages_fr.yml | 2 + src/main/resources/messages/messages_gl.yml | 3 +- src/main/resources/messages/messages_hu.yml | 2 + src/main/resources/messages/messages_id.yml | 3 +- src/main/resources/messages/messages_it.yml | 2 + src/main/resources/messages/messages_ko.yml | 3 +- src/main/resources/messages/messages_lt.yml | 3 +- src/main/resources/messages/messages_nl.yml | 2 + src/main/resources/messages/messages_pl.yml | 2 + src/main/resources/messages/messages_pt.yml | 3 +- src/main/resources/messages/messages_ro.yml | 2 + src/main/resources/messages/messages_ru.yml | 2 + src/main/resources/messages/messages_sk.yml | 3 +- src/main/resources/messages/messages_tr.yml | 2 + src/main/resources/messages/messages_uk.yml | 3 +- src/main/resources/messages/messages_vn.yml | 2 + src/main/resources/messages/messages_zhcn.yml | 2 + src/main/resources/messages/messages_zhhk.yml | 3 +- src/main/resources/messages/messages_zhmc.yml | 2 + src/main/resources/messages/messages_zhtw.yml | 3 +- .../email/ProcessCodeCommandTest.java | 126 ++++++++++++++++ .../email/RecoverEmailCommandTest.java | 136 ++---------------- .../service/PasswordRecoveryServiceTest.java | 54 +++++++ .../service/RecoveryCodeServiceTest.java | 35 +++++ 40 files changed, 529 insertions(+), 240 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java create mode 100644 src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java create mode 100644 src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java diff --git a/docs/config.md b/docs/config.md index ff2d54fb4..51c917032 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, @@ -323,6 +323,8 @@ Email: mailSMTP: 'smtp.gmail.com' # Email SMTP server port mailPort: 465 + # Only affects port 25: enable TLS/STARTTLS? + useTls: true # Email account which sends the mails mailAccount: '' # Email account password @@ -440,6 +442,8 @@ Security: length: 8 # How many hours is a recovery code valid for? validForHours: 4 + # Max number of tries to enter recovery code + maxTries: 3 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. @@ -460,4 +464,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 Feb 25 21:59:18 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Mar 06 13:51:04 EST 2017 diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java new file mode 100644 index 000000000..8a7bbf3f1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java @@ -0,0 +1,57 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.PasswordRecoveryService; +import fr.xephi.authme.service.RecoveryCodeService; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +/** + * Command for submitting email recovery code. + */ +public class ProcessCodeCommand extends PlayerCommand { + + @Inject + private CommonService commonService; + + @Inject + private DataSource dataSource; + + @Inject + private RecoveryCodeService codeService; + + @Inject + private PasswordRecoveryService recoveryService; + + @Override + protected void runCommand(Player player, List arguments) { + String name = player.getName(); + String code = arguments.get(0); + + if (codeService.hasTriesLeft(name)) { + if (codeService.isCodeValid(name, code)) { + PlayerAuth auth = dataSource.getAuth(name); + String email = auth.getEmail(); + if (email == null || "your@email.com".equalsIgnoreCase(email)) { + commonService.send(player, MessageKey.INVALID_EMAIL); + return; + } + + recoveryService.generateAndSendNewPassword(player, email); + codeService.removeCode(name); + } else { + commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE, + Integer.toString(codeService.getTriesLeft(name))); + } + } else { + codeService.removeCode(name); + commonService.send(player, MessageKey.RECOVERY_TRIES_EXCEEDED); + } + } +} 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 b3fb3b62b..455fc6134 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,34 +5,20 @@ 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.PasswordRecoveryService; 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 implements Reloadable { - - @Inject - private PasswordSecurity passwordSecurity; +public class RecoverEmailCommand extends PlayerCommand { @Inject private CommonService commonService; @@ -47,18 +33,10 @@ public class RecoverEmailCommand extends PlayerCommand implements Reloadable { private EmailService emailService; @Inject - private RecoveryCodeService recoveryCodeService; + private PasswordRecoveryService recoveryService; @Inject - private Messages messages; - - private ExpiringSet emailCooldown; - - @PostConstruct - private void initEmailCooldownSet() { - emailCooldown = new ExpiringSet<>( - commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); - } + private RecoveryCodeService recoveryCodeService; @Override protected void runCommand(Player player, List arguments) { @@ -89,73 +67,9 @@ public class RecoverEmailCommand extends PlayerCommand implements Reloadable { if (recoveryCodeService.isRecoveryCodeNeeded()) { // Process /email recovery addr@example.com - if (arguments.size() == 1) { - createAndSendRecoveryCode(player, email); - } else { - // Process /email recovery addr@example.com 12394 - processRecoveryCode(player, arguments.get(1), email); - } + recoveryService.createAndSendRecoveryCode(player, email); } else { - boolean maySendMail = checkEmailCooldown(player); - if (maySendMail) { - generateAndSendNewPassword(player, email); - } + recoveryService.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); - } - } - - private void processRecoveryCode(Player player, String code, String email) { - final String name = player.getName(); - if (recoveryCodeService.isCodeValid(name, code)) { - generateAndSendNewPassword(player, email); - recoveryCodeService.removeCode(name); - } else { - commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE); - } - } - - private void generateAndSendNewPassword(Player player, String email) { - String name = player.getName(); - String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); - HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); - - dataSource.updatePassword(name, hashNew); - 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 e8c3939a9..36c4a9cda 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -224,8 +224,11 @@ 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 */ - INCORRECT_RECOVERY_CODE("recovery_code_incorrect"), + /** The recovery code is not correct! You have %count tries remaining. */ + INCORRECT_RECOVERY_CODE("recovery_code_incorrect", "%count"), + + /** You have exceeded the maximum number of attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one. */ + RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"), /** An email was already sent recently. You must wait %time before you can send a new one. */ EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"), diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java new file mode 100644 index 000000000..272f6a952 --- /dev/null +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -0,0 +1,125 @@ +package fr.xephi.authme.service; + +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.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.concurrent.TimeUnit; + +import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; + +/** + * Manager for password recovery. + */ +public class PasswordRecoveryService implements Reloadable { + + @Inject + private CommonService commonService; + + @Inject + private RecoveryCodeService codeService; + + @Inject + private DataSource dataSource; + + @Inject + private EmailService emailService; + + @Inject + private PasswordSecurity passwordSecurity; + + @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); + } + + /** + * Create a new recovery code and send it to the player + * via email. + * + * @param player The player getting the code. + * @param email The email to send the code to. + */ + public 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); + } + } + + /** + * Generate a new password and send it to the player via + * email. This will update the database with the new password. + * + * @param player The player recovering their password. + * @param email The email to send the password to. + */ + public void generateAndSendNewPassword(Player player, String email) { + if (!checkEmailCooldown(player)) { + return; + } + + String name = player.getName(); + String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); + HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); + + dataSource.updatePassword(name, hashNew); + 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); + } + } + + /** + * Check if a player is able to have emails sent. + * + * @param player The player to check. + * @return True if the player is not on cooldown. + */ + public 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; + } + + @Override + public void reload() { + emailCooldown.setExpiration( + commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); + } +} diff --git a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java index cae8aaa73..dcccb36de 100644 --- a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java +++ b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java @@ -6,26 +6,29 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.util.expiring.ExpiringMap; +import fr.xephi.authme.util.expiring.TimedCounter; import javax.inject.Inject; import java.util.concurrent.TimeUnit; -import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID; - /** * Manager for recovery codes. */ public class RecoveryCodeService implements SettingsDependent, HasCleanup { private final ExpiringMap recoveryCodes; + private final TimedCounter playerTries; private int recoveryCodeLength; private int recoveryCodeExpiration; + private int recoveryCodeMaxTries; @Inject RecoveryCodeService(Settings settings) { recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH); recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID); + recoveryCodeMaxTries = settings.getProperty(SecuritySettings.RECOVERY_CODE_MAX_TRIES); recoveryCodes = new ExpiringMap<>(recoveryCodeExpiration, TimeUnit.HOURS); + playerTries = new TimedCounter<>(recoveryCodeExpiration, TimeUnit.HOURS); } /** @@ -43,6 +46,8 @@ public class RecoveryCodeService implements SettingsDependent, HasCleanup { */ public String generateCode(String player) { String code = RandomStringUtils.generateHex(recoveryCodeLength); + + playerTries.put(player, recoveryCodeMaxTries); recoveryCodes.put(player, code); return code; } @@ -56,9 +61,30 @@ public class RecoveryCodeService implements SettingsDependent, HasCleanup { */ public boolean isCodeValid(String player, String code) { String storedCode = recoveryCodes.get(player); + playerTries.decrement(player); return storedCode != null && storedCode.equals(code); } + /** + * Checks whether a player has tries remaining to enter a code. + * + * @param player The player to check for. + * @return True if the player has tries left. + */ + public boolean hasTriesLeft(String player) { + return playerTries.get(player) > 0; + } + + /** + * Get the number of attempts a player has to enter a code. + * + * @param player The player to check for. + * @return The number of tries left. + */ + public int getTriesLeft(String player) { + return playerTries.get(player); + } + /** * Removes the player's recovery code if present. * @@ -66,17 +92,20 @@ public class RecoveryCodeService implements SettingsDependent, HasCleanup { */ public void removeCode(String player) { recoveryCodes.remove(player); + playerTries.remove(player); } @Override public void reload(Settings settings) { recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH); - recoveryCodeExpiration = settings.getProperty(RECOVERY_CODE_HOURS_VALID); + recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID); + recoveryCodeMaxTries = settings.getProperty(SecuritySettings.RECOVERY_CODE_MAX_TRIES); recoveryCodes.setExpiration(recoveryCodeExpiration, TimeUnit.HOURS); } @Override public void performCleanup() { recoveryCodes.removeExpiredEntries(); + playerTries.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 496fb3b2b..0aa517aeb 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,10 @@ public class SecuritySettings implements SettingsHolder { public static final Property RECOVERY_CODE_HOURS_VALID = newProperty("Security.recoveryCode.validForHours", 4); + @Comment("Max number of tries to enter recovery code") + public static final Property RECOVERY_CODE_MAX_TRIES = + newProperty("Security.recoveryCode.maxTries", 3); + @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." 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 c3ae908cd..80367bdd5 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java +++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java @@ -35,6 +35,21 @@ public class TimedCounter extends ExpiringMap { put(key, get(key) + 1); } + /** + * Decrements the value stored for the provided key. + * This method will NOT update the expiration. + * + * @param key the key to increment the counter for + */ + public void decrement(K key) { + ExpiringEntry e = entries.get(key); + + if (e != null) { + if (e.getValue() <= 0) { remove(key); } + else {entries.put(key, new ExpiringEntry<>(e.getValue() - 1, e.getExpiration())); } + } + } + /** * Calculates the total of all non-expired entries in this counter. * diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 38e79face..c4042804a 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' usage_unreg: '&cКоманда: /unregister парола' pwd_changed: '&cПаролата е променена!' diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index c213f49cb..e39c553c3 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -48,7 +48,9 @@ accounts_owned_self: 'Você tem %count contas:' accounts_owned_other: 'O jogador %name tem %count contas:' two_factor_create: '&2O seu código secreto é %code. Você pode verificá-lo a partir daqui %url' recovery_code_sent: 'Um código de recuperação para redefinir sua senha foi enviada para o seu e-mail.' +# TODO: Missing tags %count recovery_code_incorrect: 'O código de recuperação esta incorreto! Use /email recovery [email] para gerar um novo!' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cA sua conta ainda não está ativada, por favor, verifique seus e-mails!' usage_unreg: '&cUse: /unregister ' pwd_changed: '&2Senha alterada com sucesso!' diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 586a7376f..fb8ef0f8b 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Vlastníš tyto účty (%count):' accounts_owned_other: 'Hráč %name vlastní tyto účty (%count):' two_factor_create: '&2Tvůj tajný kód je %code. Můžeš ho oskenovat zde %url' recovery_code_sent: 'Kód pro obnovení hesla byl odeslán na váš email.' +# TODO: Missing tags %count recovery_code_incorrect: 'Kód pro není správný! Použijte příkaz /email recovery [email] pro vygenerování nového.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cTvůj účet není aktivovaný, zkontroluj si svůj E-mail.' usage_unreg: '&cPoužij: "/unregister TvojeHeslo".' pwd_changed: '&cHeslo změněno!' diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 648a5153f..02fcfad49 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Du besitzt %count Accounts:' accounts_owned_other: 'Der Spieler %name hat %count Accounts:' two_factor_create: '&2Dein geheimer Code ist %code. Du kannst ihn hier abfragen: %url' recovery_code_sent: 'Ein Wiederherstellungscode zum Zurücksetzen deines Passworts wurde an deine E-Mail-Adresse geschickt.' +# TODO: Missing tags %count recovery_code_incorrect: 'Der Wiederherstellungscode stimmt nicht! Nutze /email recovery [email] um einen neuen zu generieren.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cDein Account wurde noch nicht aktiviert. Bitte prüfe deine E-Mails!' usage_unreg: '&cBenutze: /unregister ' pwd_changed: '&2Passwort geändert!' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index c71902341..cf22eb0b3 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -44,7 +44,8 @@ accounts_owned_self: 'You own %count accounts:' accounts_owned_other: 'The player %name has %count accounts:' two_factor_create: '&2Your secret code is %code. You can scan it from here %url' recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' -recovery_code_incorrect: 'The recovery code is not correct! Use "/email recovery [email]" to generate a new one' +recovery_code_incorrect: 'The recovery code is not correct! You have %count tries remaining.' +recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!' usage_unreg: '&cUsage: /unregister ' pwd_changed: '&2Password changed successfully!' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index ac7415718..6c6508057 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -47,7 +47,9 @@ accounts_owned_self: 'Eres propietario de %count cuentas:' accounts_owned_other: 'El jugador %name tiene %count cuentas:' two_factor_create: '&2Tu código secreto es %code. Lo puedes escanear desde aquí %url' recovery_code_sent: 'El código de recuperación para recuperar tu contraseña se ha enviado a tu correo.' +# TODO: Missing tags %count recovery_code_incorrect: '¡El código de recuperación no es correcto! Usa "/email recovery [email]" para generar uno nuevo' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&fTu cuenta no está activada aún, ¡revisa tu correo!' usage_unreg: '&cUso: /unregister contraseña' pwd_changed: '&c¡Contraseña cambiada!' diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 05473e68a..648d7f209 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!' diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index a48ac74db..e3a44396e 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!!' diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 9070db19d..9a5b6dcad 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -49,7 +49,9 @@ accounts_owned_self: 'Vous avez %count comptes:' accounts_owned_other: 'Le joueur %name a %count comptes:' two_factor_create: '&aVotre code secret est &2%code&a. Vous pouvez le scanner depuis &2%url' recovery_code_sent: 'Un code de récupération a été envoyé à votre adresse email afin de réinitialiser votre mot de passe.' +# TODO: Missing tags %count recovery_code_incorrect: '&cLe code de réinitialisation est incorrect!%nl%Faites "/email recovery [email]" pour en générer un nouveau.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&fCe compte n''est pas actif, consultez vos emails !' usage_unreg: '&cPour supprimer votre compte, utilisez "/unregister "' pwd_changed: '&aMot de passe changé avec succès !' diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index 1c0570306..da7270bc0 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index dc62f8c18..a77dfd6c2 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -44,7 +44,9 @@ accounts_owned_self: '%count db regisztrációd van:' accounts_owned_other: 'A %name nevű játékosnak, %count db regisztrációja van:' two_factor_create: '&2A te titkos kódod a következő: %code. Vagy skenneld be a következő oldalról: %url' recovery_code_sent: 'A jelszavad visszaállításához szükséges kódot sikeresen kiküldtük az email címedre!' +# TODO: Missing tags %count recovery_code_incorrect: 'A visszaállító kód helytelen volt! Használd a következő parancsot: /email recovery [email címed] egy új generálásához' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cA felhasználód aktiválása még nem történt meg, ellenőrizd a megadott emailed!' usage_unreg: '&cHasználat: "/unregister "' pwd_changed: '&cJelszó sikeresen megváltoztatva!' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index 90db87bd8..c5c9caf67 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!' diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 989b555ba..246fa3bce 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -46,7 +46,9 @@ accounts_owned_self: 'Possiedi %count account:' accounts_owned_other: 'Il giocatore %name possiede %count account:' two_factor_create: '&2Il tuo codice segreto è: &f%code%%nl%&2Puoi anche scannerizzare il codice QR da qui: &f%url' recovery_code_sent: 'Una email contenente il codice di recupero per reimpostare la tua password è stata appena inviata al tuo indirizzo email.' +# TODO: Missing tags %count recovery_code_incorrect: 'Il codice di recupero inserito non è corretto! Scrivi "/email recovery " per generarne uno nuovo' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cIl tuo account non è stato ancora verificato, controlla fra le tue email per scoprire come attivarlo!' usage_unreg: '&cUtilizzo: /unregister ' pwd_changed: '&2Password cambiata correttamente!' diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index a65d93526..cfb6fcb57 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -49,7 +49,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!' usage_unreg: '&c사용법: /unregister 비밀번호' pwd_changed: '&c비밀번호를 변경했습니다!' diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index d75dec4a1..8c144a808 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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' diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 630fadd6e..8ebff6db5 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Je bezit %count accounts:' accounts_owned_other: 'De speler %name heeft %count accounts:' two_factor_create: '&2Je geheime code is %code. Je kunt hem scannen op %url' recovery_code_sent: 'Een herstelcode voor je wachtwoord is naar je mailbox gestuurd.' +# TODO: Missing tags %count recovery_code_incorrect: 'De herstelcode is niet correct! Gebruik "/email recovery [email]" om een nieuwe te krijgen' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: 'Je account is nog niet geactiveerd, controleer je mailbox!' usage_unreg: '&cGebruik: /unregister password' pwd_changed: '&cWachtwoord succesvol aangepast!' diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 35622e5d4..4600c6585 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -45,7 +45,9 @@ accounts_owned_self: 'Posiadasz %count kont:' accounts_owned_other: 'Gracz %name posiada %count kont:' two_factor_create: '&2Twoj sekretny kod to %code. Mozesz zeskanowac go tutaj %url' recovery_code_sent: 'Kod odzyskiwania hasla zostal wyslany na adres email przypisany do konta.' +# TODO: Missing tags %count recovery_code_incorrect: 'Kod odzyskiwania hasla jest bledny! Uzyj /email recovery [email] aby wygenerowac nowy.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&fTwoje konto nie zostalo aktywowane! Sprawdz maila.' usage_unreg: '&cUzycie: /unregister haslo' pwd_changed: '&fHaslo zostalo zmienione!' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index fef35f74f..f4af6e811 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -45,7 +45,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index 703b93f59..6df8a7c18 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Detii %count conturi:' accounts_owned_other: 'Jucatorul %name are %count conturi:' two_factor_create: '&2Codul tau secret este %code. Il poti scana de aici %url' recovery_code_sent: 'Un cod de recuperare a parolei a fost trimis catre email-ul tau.' +# TODO: Missing tags %count recovery_code_incorrect: 'Codul de recuperare nu este corect! Foloseste /email recovery [email] pentru a genera unul nou.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cContul tau nu este activat, te rugam verifica-ti email-ul!' usage_unreg: '&cFoloseste comanda: /unregister ' pwd_changed: '&2Parola a fost inregistrata cu succes!' diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index ed4e1729f..17c2170cc 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Вы являетесь владельцем %count акк accounts_owned_other: 'Игрок %name имеет %count аккаунтов:' two_factor_create: '&2Ваш секретный код %code. Вы должны просканировать его здесь %url' recovery_code_sent: 'Код восстановления для сброса пароля был отправлен на вашу электронную почту.' +# TODO: Missing tags %count recovery_code_incorrect: 'Код восстановления неверный! Введите /email recovery <Ваш Email>, чтобы отправить новый код' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!' usage_unreg: '&cИспользование: &e/unregister <Пароль>' pwd_changed: '&2Пароль изменен!' diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index b49f8cf5c..901741e27 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -49,7 +49,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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é!' diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 3431e99e6..9bb62f1f0 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -44,7 +44,9 @@ 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' recovery_code_sent: 'Sifre sifirlama kodu eposta adresinize gonderildi.' +# TODO: Missing tags %count recovery_code_incorrect: 'Kod dogru degil! Kullanim "/email recovery [eposta]" ile yeni bir kod olustur' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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!' diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index 35dbbffee..dc63d3167 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -44,7 +44,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cВаш акаунт ще не активовано. Будь ласка, провірте свою електронну пошту!' usage_unreg: '&cСинтаксис: /unregister <пароль>' pwd_changed: '&2Пароль успішно змінено!' diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index 7f68be063..b51f4ced5 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -44,7 +44,9 @@ accounts_owned_self: 'Bạn sở hữu %count tài khoản:' accounts_owned_other: 'Người chơi %name có %count tài khoản:' two_factor_create: '&2Mã bí mật của bạn là %code. Bạn có thể quét nó tại đây %url' recovery_code_sent: 'Một mã khôi phục mật khẩu đã được gửi đến địa chỉ email của bạn.' +# TODO: Missing tags %count recovery_code_incorrect: 'Mã khôi phục không đúng! Dùng lệnh /email recovery [email] để tạo một mã mới' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&cTài khoản của bạn chưa được kích hoạt, vui lòng kiểm tra email!' usage_unreg: '&cSử dụng: /unregister ' pwd_changed: '&2Thay đổi mật khẩu thành công!' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 63922334d..0c0e8d0ab 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -45,7 +45,9 @@ accounts_owned_self: '您拥有 %count 个账户:' accounts_owned_other: '玩家 %name 拥有 %count 个账户:' two_factor_create: '&8[&6玩家系统&8] &2你的代码是 %code,你可以使用 %url 来扫描' recovery_code_sent: '一个用于重置您的密码的验证码已发到您的邮箱' +# TODO: Missing tags %count recovery_code_incorrect: '验证码不正确! 使用 /email recovery [email] 以生成新的验证码' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' usage_unreg: '&8[&6玩家系统&8] &c正确用法:“/unregister <密码>”' pwd_changed: '&8[&6玩家系统&8] &c密码已成功修改!' diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 896003d4f..7a3335a2e 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -49,7 +49,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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你成功更換了你的密碼 !' diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index 92f9113ea..3eeee554a 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -44,7 +44,9 @@ accounts_owned_self: '您擁有 %count 個帳戶:' accounts_owned_other: '玩家 %name 擁有 %count 個帳戶:' two_factor_create: '&2您的密碼是 %code。您可以從這裡掃描 %url' recovery_code_sent: '已將重設密碼的恢復代碼發送到您的電子郵件。' +# TODO: Missing tags %count recovery_code_incorrect: '恢復代碼錯誤!使用指令: "/email recovery [電郵地址]" 生成新的一個恢復代碼。' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' vb_nonActiv: '&c你的帳戶未激活,請確認電郵!' usage_unreg: '&c使用方法: "/unregister <你的密碼>"' pwd_changed: '&2密碼已更變!' diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index b2cff0384..e1129db75 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -49,7 +49,8 @@ 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! You have %count tries remaining.' +# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. 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密碼變更成功!' diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java new file mode 100644 index 000000000..076839ee7 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java @@ -0,0 +1,126 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.PasswordRecoveryService; +import fr.xephi.authme.service.RecoveryCodeService; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Tests for {@link ProcessCodeCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class ProcessCodeCommandTest { + + @InjectMocks + private ProcessCodeCommand command; + + @Mock + private CommonService commonService; + + @Mock + private DataSource dataSource; + + @Mock + private RecoveryCodeService codeService; + + @Mock + private PasswordRecoveryService recoveryService; + + private static final String DEFAULT_EMAIL = "your@email.com"; + + @Test + public void shouldSendErrorForInvalidRecoveryCode() { + // given + String name = "Vultur3"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(codeService.hasTriesLeft(name)).willReturn(true); + given(codeService.isCodeValid(name, "bogus")).willReturn(false); + given(codeService.getTriesLeft(name)).willReturn(2); + + // when + command.executeCommand(sender, Collections.singletonList("bogus")); + + // then + verify(commonService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE, "2"); + verifyNoMoreInteractions(recoveryService); + } + + @Test + public void shouldSendErrorForNoMoreTries() { + // given + String name = "BobbY"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(codeService.hasTriesLeft(name)).willReturn(false); + + // when + command.executeCommand(sender, Collections.singletonList("bogus")); + + // then + verify(commonService).send(sender, MessageKey.RECOVERY_TRIES_EXCEEDED); + verify(codeService).removeCode(name); + verifyNoMoreInteractions(recoveryService); + } + + @Test + public void shouldHandleDefaultEmail() { + // given + String name = "Tract0r"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(DEFAULT_EMAIL)); + given(codeService.hasTriesLeft(name)).willReturn(true); + given(codeService.isCodeValid(name, "actual")).willReturn(true); + + // when + command.executeCommand(sender, Collections.singletonList("actual")); + + // then + verify(dataSource).getAuth(name); + verifyNoMoreInteractions(dataSource); + verify(commonService).send(sender, MessageKey.INVALID_EMAIL); + } + + @Test + public void shouldGenerateAndSendPassword() { + // given + String name = "GenericName"; + Player sender = mock(Player.class); + given(sender.getName()).willReturn(name); + String email = "ran-out@example.com"; + PlayerAuth auth = newAuthWithEmail(email); + given(dataSource.getAuth(name)).willReturn(auth); + given(codeService.hasTriesLeft(name)).willReturn(true); + given(codeService.isCodeValid(name, "actual")).willReturn(true); + + // when + command.executeCommand(sender, Collections.singletonList("actual")); + + // then + verify(recoveryService).generateAndSendNewPassword(sender, email); + verify(codeService).removeCode(name); + } + + private static PlayerAuth newAuthWithEmail(String email) { + return PlayerAuth.builder() + .name("name") + .email(email) + .build(); + } +} 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 5b1020780..8b026fc52 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 @@ -9,38 +9,22 @@ 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.PasswordRecoveryService; 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.Mock; -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; -import static org.mockito.ArgumentMatchers.eq; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -70,13 +54,13 @@ public class RecoverEmailCommandTest { @Mock private EmailService emailService; + + @Mock + private PasswordRecoveryService recoveryService; @Mock private RecoveryCodeService recoveryCodeService; - @Mock - private Messages messages; - @BeforeClass public static void initLogger() { TestHelper.setupLogger(); @@ -200,85 +184,21 @@ public class RecoverEmailCommandTest { // then verify(emailService).hasAllInformation(); verify(dataSource).getAuth(name); - verify(recoveryCodeService).generateCode(name); - verify(commonService).send(sender, MessageKey.RECOVERY_CODE_SENT); - verify(emailService).sendRecoveryCode(name, email, code); - } - - @Test - public void shouldSendErrorForInvalidRecoveryCode() { - // given - String name = "Vultur3"; - Player sender = mock(Player.class); - given(sender.getName()).willReturn(name); - given(emailService.hasAllInformation()).willReturn(true); - given(playerCache.isAuthenticated(name)).willReturn(false); - String email = "vulture@example.com"; - PlayerAuth auth = newAuthWithEmail(email); - given(dataSource.getAuth(name)).willReturn(auth); - given(recoveryCodeService.isRecoveryCodeNeeded()).willReturn(true); - given(recoveryCodeService.isCodeValid(name, "bogus")).willReturn(false); - - // when - command.executeCommand(sender, Arrays.asList(email, "bogus")); - - // then - verify(emailService).hasAllInformation(); - verify(dataSource, only()).getAuth(name); - verify(commonService).send(sender, MessageKey.INCORRECT_RECOVERY_CODE); - verifyNoMoreInteractions(emailService); - } - - @Test - public void shouldResetPasswordAndSendEmail() { - // given - String name = "Vultur3"; - Player sender = mock(Player.class); - given(sender.getName()).willReturn(name); - 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"; - PlayerAuth auth = newAuthWithEmail(email); - given(dataSource.getAuth(name)).willReturn(auth); - 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); - given(recoveryCodeService.isCodeValid(name, code)).willReturn(true); - - // when - command.executeCommand(sender, Arrays.asList(email, code)); - - // then - 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(recoveryCodeService).removeCode(name); - verify(emailService).sendPasswordMail(name, email, generatedPassword); - verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + verify(recoveryService).createAndSendRecoveryCode(sender, email); } @Test public void shouldGenerateNewPasswordWithoutRecoveryCode() { // given - String name = "sh4rK"; + String name = "Vultur3"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); given(emailService.hasAllInformation()).willReturn(true); given(emailService.sendPasswordMail(anyString(), anyString(), anyString())).willReturn(true); given(playerCache.isAuthenticated(name)).willReturn(false); - String email = "shark@example.org"; + String email = "vulture@example.com"; PlayerAuth auth = newAuthWithEmail(email); given(dataSource.getAuth(name)).willReturn(auth); - 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); // when @@ -287,49 +207,9 @@ public class RecoverEmailCommandTest { // then 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(emailService).sendPasswordMail(name, email, generatedPassword); - verify(commonService).send(sender, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); + verify(recoveryService).generateAndSendNewPassword(sender, email); } - @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"); - } - - private static PlayerAuth newAuthWithEmail(String email) { return PlayerAuth.builder() .name("name") diff --git a/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java b/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java new file mode 100644 index 000000000..c7c1419d1 --- /dev/null +++ b/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java @@ -0,0 +1,54 @@ +package fr.xephi.authme.service; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.mail.EmailService; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.settings.properties.SecuritySettings; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link PasswordRecoveryService}. + */ +@Ignore +@RunWith(MockitoJUnitRunner.class) +public class PasswordRecoveryServiceTest { + + @InjectDelayed + private PasswordRecoveryService recoveryService; + + @Mock + private CommonService commonService; + + @Mock + private RecoveryCodeService codeService; + + @Mock + private DataSource dataSource; + + @Mock + private EmailService emailService; + + @Mock + private PasswordSecurity passwordSecurity; + + @Mock + private RecoveryCodeService recoveryCodeService; + + @Mock + private Messages messages; + + @BeforeInjecting + public void initSettings() { + given(commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS)).willReturn(40); + } + + //TODO: Write tests +} diff --git a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java index eb8af0dc3..acd26d933 100644 --- a/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/RecoveryCodeServiceTest.java @@ -33,6 +33,7 @@ public class RecoveryCodeServiceTest { public void initSettings() { given(settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)).willReturn(4); given(settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH)).willReturn(5); + given(settings.getProperty(SecuritySettings.RECOVERY_CODE_MAX_TRIES)).willReturn(3); } @Test @@ -62,6 +63,35 @@ public class RecoveryCodeServiceTest { assertThat(code, stringWithLength(5)); } + @Test + public void playerHasTriesLeft() { + // given + String player = "Dusty"; + recoveryCodeService.generateCode(player); + + // when + boolean result = recoveryCodeService.hasTriesLeft(player); + + // then + assertThat(result, equalTo(true)); + } + + @Test + public void playerHasNoTriesLeft() { + // given + String player = "Dusty"; + recoveryCodeService.generateCode(player); + recoveryCodeService.isCodeValid(player, "1st try"); + recoveryCodeService.isCodeValid(player, "2nd try"); + recoveryCodeService.isCodeValid(player, "3rd try"); + + // when + boolean result = recoveryCodeService.hasTriesLeft(player); + + // then + assertThat(result, equalTo(false)); + } + @Test public void shouldRecognizeCorrectCode() { // given @@ -87,10 +117,15 @@ public class RecoveryCodeServiceTest { // then assertThat(recoveryCodeService.isCodeValid(player, code), equalTo(false)); assertThat(getCodeMap().get(player), nullValue()); + assertThat(getTriesCounter().get(player), equalTo(0)); } private ExpiringMap getCodeMap() { return ReflectionTestUtils.getFieldValue(RecoveryCodeService.class, recoveryCodeService, "recoveryCodes"); } + + private ExpiringMap getTriesCounter() { + return ReflectionTestUtils.getFieldValue(RecoveryCodeService.class, recoveryCodeService, "playerTries"); + } } From fa65b1af5edf017b9ea3897fdaea5fc2cde43fa1 Mon Sep 17 00:00:00 2001 From: EbonJaguar Date: Mon, 6 Mar 2017 14:15:07 -0500 Subject: [PATCH 066/125] make TimedCounter#decrement(K) easier to read --- .../java/fr/xephi/authme/util/expiring/TimedCounter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 80367bdd5..2e19b5a28 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java +++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java @@ -45,8 +45,12 @@ public class TimedCounter extends ExpiringMap { ExpiringEntry e = entries.get(key); if (e != null) { - if (e.getValue() <= 0) { remove(key); } - else {entries.put(key, new ExpiringEntry<>(e.getValue() - 1, e.getExpiration())); } + if (e.getValue() <= 0) { + remove(key); + } + else { + entries.put(key, new ExpiringEntry<>(e.getValue() - 1, e.getExpiration())); + } } } From 62c053d5cb5b0097922c4d7d0447b25ed1bbba6f Mon Sep 17 00:00:00 2001 From: EbonJaguar Date: Mon, 6 Mar 2017 15:21:49 -0500 Subject: [PATCH 067/125] Register the new /email code command --- .../fr/xephi/authme/command/CommandInitializer.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 98030c963..6758c0086 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -30,6 +30,7 @@ import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand; import fr.xephi.authme.command.executable.email.AddEmailCommand; import fr.xephi.authme.command.executable.email.ChangeEmailCommand; import fr.xephi.authme.command.executable.email.EmailBaseCommand; +import fr.xephi.authme.command.executable.email.ProcessCodeCommand; import fr.xephi.authme.command.executable.email.RecoverEmailCommand; import fr.xephi.authme.command.executable.email.ShowEmailCommand; import fr.xephi.authme.command.executable.login.LoginCommand; @@ -419,11 +420,21 @@ public class CommandInitializer { .detailedDescription("Recover your account using an Email address by sending a mail containing " + "a new password.") .withArgument("email", "Email address", false) - .withArgument("code", "Recovery code", true) .permission(PlayerPermission.RECOVER_EMAIL) .executableCommand(RecoverEmailCommand.class) .register(); + // Register the process recovery code command + CommandDescription.builder() + .parent(EMAIL_BASE) + .labels("code") + .description("Submit code to recover password") + .detailedDescription("Recover your account by submitting an emailed code to your email.") + .withArgument("code", "Recovery code", false) + .permission(PlayerPermission.RECOVER_EMAIL) + .executableCommand(ProcessCodeCommand.class) + .register(); + // Register the base captcha command CommandDescription CAPTCHA_BASE = CommandDescription.builder() .parent(null) From 4bb10c5d6d6b142d5001ee89c99aaa9925cbcb62 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 7 Mar 2017 20:35:48 +0100 Subject: [PATCH 068/125] #1113 Handle LimboPlayer tasks via LimboService - Add methods to LimboService for handling messages to make it the only relevant Limbo class for outside classes - Move LimboPlayerTaskManager to limbo package and make it package-private - Create MessageTask and TimeoutTask immediately when LimboPlayer is created - #1112 MessageTask: improve efficiency by keeping reference to Player --- .../executable/captcha/CaptchaCommand.java | 6 +- .../data/limbo/LimboPlayerTaskManager.java | 97 +++++++++++++ .../xephi/authme/data/limbo/LimboService.java | 68 ++++++++- .../authme/process/join/AsynchronousJoin.java | 8 +- .../process/login/AsynchronousLogin.java | 10 +- .../ProcessSynchronousPlayerLogout.java | 15 +- .../register/ProcessSyncEmailRegister.java | 8 +- .../register/ProcessSyncPasswordRegister.java | 8 +- .../unregister/AsynchronousUnregister.java | 9 +- .../authme/task/LimboPlayerTaskManager.java | 131 ------------------ .../fr/xephi/authme/task/MessageTask.java | 29 +--- .../captcha/CaptchaCommandTest.java | 6 +- .../limbo}/LimboPlayerTaskManagerTest.java | 68 ++------- .../process/login/AsynchronousLoginTest.java | 6 +- .../AsynchronousUnregisterTest.java | 7 +- 15 files changed, 207 insertions(+), 269 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java delete mode 100644 src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java rename src/test/java/fr/xephi/authme/{task => data/limbo}/LimboPlayerTaskManagerTest.java (72%) 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 e87264903..259e20f96 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,9 +3,9 @@ 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.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -23,7 +23,7 @@ public class CaptchaCommand extends PlayerCommand { private CommonService commonService; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Override public void runCommand(Player player, List arguments) { @@ -43,7 +43,7 @@ public class CaptchaCommand extends PlayerCommand { if (isCorrectCode) { commonService.send(player, MessageKey.CAPTCHA_SUCCESS); commonService.send(player, MessageKey.LOGIN_MESSAGE); - limboPlayerTaskManager.unmuteMessageTask(player); + limboService.unmuteMessageTask(player); } else { String newCode = captchaManager.generateCode(player.getName()); commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode); diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java new file mode 100644 index 000000000..4e0c0a336 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.data.limbo; + +import fr.xephi.authme.data.auth.PlayerCache; +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.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.task.TimeoutTask; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import javax.inject.Inject; + +import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; + +/** + * Registers tasks associated with a LimboPlayer. + */ +class LimboPlayerTaskManager { + + @Inject + private Messages messages; + + @Inject + private Settings settings; + + @Inject + private BukkitService bukkitService; + + @Inject + private PlayerCache playerCache; + + LimboPlayerTaskManager() { + } + + /** + * Registers a {@link MessageTask} for the given player name. + * + * @param player the player + * @param limbo the associated limbo player of the player + * @param isRegistered whether the player is registered or not + * (false shows "please register", true shows "please log in") + */ + void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) { + int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL); + MessageKey key = getMessageKey(isRegistered); + if (interval > 0) { + MessageTask messageTask = new MessageTask(player, messages.retrieve(key)); + bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND); + limbo.setMessageTask(messageTask); + } + } + + /** + * Registers a {@link TimeoutTask} for the given player according to the configuration. + * + * @param player the player to register a timeout task for + * @param limbo the associated limbo player + */ + void registerTimeoutTask(Player player, LimboPlayer limbo) { + final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; + if (timeout > 0) { + String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); + BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout); + limbo.setTimeoutTask(task); + } + } + + /** + * Null-safe method to set the muted flag on a message task. + * + * @param task the task to modify (or null) + * @param isMuted the value to set if task is not null + */ + static void setMuted(MessageTask task, boolean isMuted) { + if (task != null) { + task.setMuted(isMuted); + } + } + + /** + * Returns the appropriate message key according to the registration status and settings. + * + * @param isRegistered whether or not the username is registered + * @return the message key to display to the user + */ + private static MessageKey getMessageKey(boolean isRegistered) { + if (isRegistered) { + return MessageKey.LOGIN_MESSAGE; + } else { + return MessageKey.REGISTER_MESSAGE; + } + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index 0333a1651..90b96d378 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -10,6 +10,7 @@ import org.bukkit.entity.Player; import javax.inject.Inject; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** @@ -18,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap; */ public class LimboService { - private Map entries = new ConcurrentHashMap<>(); + private final Map entries = new ConcurrentHashMap<>(); @Inject private SpawnLoader spawnLoader; @@ -29,6 +30,9 @@ public class LimboService { @Inject private Settings settings; + @Inject + private LimboPlayerTaskManager taskManager; + LimboService() { } @@ -62,6 +66,7 @@ public class LimboService { player.setWalkSpeed(walkSpeed); player.setFlySpeed(flySpeed); limbo.clearTasks(); + ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName); } } @@ -89,8 +94,9 @@ public class LimboService { * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player. * * @param player the player to process + * @param isRegistered whether or not the player is registered */ - public void createLimboPlayer(Player player) { + public void createLimboPlayer(Player player, boolean isRegistered) { final String name = player.getName().toLowerCase(); LimboPlayer existingLimbo = entries.remove(name); @@ -100,10 +106,53 @@ public class LimboService { } LimboPlayer limboPlayer = newLimboPlayer(player); + taskManager.registerMessageTask(player, limboPlayer, isRegistered); + taskManager.registerTimeoutTask(player, limboPlayer); revokeLimboStates(player); entries.put(name, limboPlayer); } + /** + * Creates new tasks for the given player and cancels the old ones for a newly registered player. + * This resets his time to log in (TimeoutTask) and updates the messages he is shown (MessageTask). + * + * @param player the player to reset the tasks for + */ + public void replaceTasksAfterRegistration(Player player) { + getLimboOrLogError(player, "reset tasks") + .ifPresent(limbo -> { + taskManager.registerTimeoutTask(player, limbo); + taskManager.registerMessageTask(player, limbo, true); + }); + } + + /** + * Resets the message task associated with the player's LimboPlayer. + * + * @param player the player to set a new message task for + * @param isRegistered whether or not the player is registered + */ + public void resetMessageTask(Player player, boolean isRegistered) { + getLimboOrLogError(player, "reset message task") + .ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered)); + } + + /** + * @param player the player whose message task should be muted + */ + public void muteMessageTask(Player player) { + getLimboOrLogError(player, "mute message task") + .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), true)); + } + + /** + * @param player the player whose message task should be unmuted + */ + public void unmuteMessageTask(Player player) { + getLimboOrLogError(player, "unmute message task") + .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false)); + } + /** * Creates a LimboPlayer with the given player's details. * @@ -143,4 +192,19 @@ public class LimboService { player.setWalkSpeed(0.0f); } } + + /** + * Returns the limbo player for the given player or logs an error. + * + * @param player the player to retrieve the limbo player for + * @param context the action for which the limbo player is being retrieved (for logging) + * @return Optional with the limbo player + */ + private Optional getLimboOrLogError(Player player, String context) { + LimboPlayer limbo = entries.get(player.getName().toLowerCase()); + if (limbo == null) { + ConsoleLogger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context); + } + return Optional.ofNullable(limbo); + } } 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 af55ec654..20c2ddd49 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -20,7 +20,6 @@ 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.util.PlayerUtils; import org.bukkit.GameMode; import org.bukkit.Server; @@ -62,9 +61,6 @@ public class AsynchronousJoin implements AsynchronousProcess { @Inject private BukkitService bukkitService; - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; - @Inject private AsynchronousLogin asynchronousLogin; @@ -153,9 +149,7 @@ public class AsynchronousJoin implements AsynchronousProcess { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { // TODO #1113: Find an elegant way to deop unregistered players (and disable fly status etc.?) - limboService.createLimboPlayer(player); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); + limboService.createLimboPlayer(player, isAuthAvailable); player.setNoDamageTicks(registrationTimeout); if (pluginHookService.isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) { 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 1e16b660a..7ae0b3a87 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -6,6 +6,7 @@ import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.TempbanManager; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.message.MessageKey; @@ -23,7 +24,6 @@ import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; @@ -66,7 +66,7 @@ public class AsynchronousLogin implements AsynchronousProcess { private TempbanManager tempbanManager; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; AsynchronousLogin() { } @@ -114,8 +114,7 @@ public class AsynchronousLogin implements AsynchronousProcess { if (auth == null) { service.send(player, MessageKey.UNKNOWN_USER); // Recreate the message task to immediately send the message again as response - // and to make sure we send the right register message (password vs. email registration) - limboPlayerTaskManager.registerMessageTask(name, false); + limboService.resetMessageTask(player, false); return null; } @@ -190,7 +189,7 @@ public class AsynchronousLogin implements AsynchronousProcess { // If the authentication fails check if Captcha is required and send a message to the player if (captchaManager.isCaptchaRequired(player.getName())) { - limboPlayerTaskManager.muteMessageTask(player); + limboService.muteMessageTask(player); service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(player.getName())); } @@ -241,7 +240,6 @@ public class AsynchronousLogin implements AsynchronousProcess { // task, we schedule it in the end // so that we can be sure, and have not to care if it might be // processed in other order. - limboPlayerTaskManager.clearTasks(player); syncProcessManager.processSyncPlayerLogin(player); } else { ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted..."); 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 ffafe16b7..028781587 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java @@ -7,13 +7,12 @@ import fr.xephi.authme.events.LogoutEvent; import fr.xephi.authme.listener.protocollib.ProtocolLibService; 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.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.TeleportationService; 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.service.TeleportationService; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -34,9 +33,6 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { @Inject private ProtocolLibService protocolLibService; - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; - @Inject private LimboService limboService; @@ -59,9 +55,6 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { applyLogoutEffect(player); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, true); - // Player is now logout... Time to fire event ! bukkitService.callEvent(new LogoutEvent(player)); @@ -81,7 +74,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess { } // Set player's data to unauthenticated - limboService.createLimboPlayer(player); + limboService.createLimboPlayer(player, true); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED); } 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 cebbcdcb2..4e57b973b 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java @@ -1,11 +1,11 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -18,7 +18,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { private CommonService service; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; ProcessSyncEmailRegister() { } @@ -27,9 +27,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess { 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); + limboService.replaceTasksAfterRegistration(player); player.saveData(); ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player)); diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java index c500f1556..173cc0d82 100644 --- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java @@ -1,6 +1,7 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.process.SynchronousProcess; @@ -9,7 +10,6 @@ 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.RegistrationSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.util.PlayerUtils; import org.bukkit.entity.Player; @@ -26,7 +26,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { private CommonService service; @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Inject private CommandManager commandManager; @@ -40,9 +40,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess { * @param player the player */ private void requestLogin(Player player) { - final String name = player.getName().toLowerCase(); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, true); + limboService.replaceTasksAfterRegistration(player); if (player.isInsideVehicle() && player.getVehicle() != null) { player.getVehicle().eject(); diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 667901560..eabd902eb 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -15,7 +15,6 @@ import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; @@ -45,9 +44,6 @@ public class AsynchronousUnregister implements AsynchronousProcess { @Inject private LimboService limboService; - @Inject - private LimboPlayerTaskManager limboPlayerTaskManager; - @Inject private TeleportationService teleportationService; @@ -112,10 +108,7 @@ public class AsynchronousUnregister implements AsynchronousProcess { player.saveData(); bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { - limboService.createLimboPlayer(player); - limboPlayerTaskManager.registerTimeoutTask(player); - limboPlayerTaskManager.registerMessageTask(name, false); - + limboService.createLimboPlayer(player, false); applyBlindEffect(player); }); } diff --git a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java deleted file mode 100644 index f66f9445d..000000000 --- a/src/main/java/fr/xephi/authme/task/LimboPlayerTaskManager.java +++ /dev/null @@ -1,131 +0,0 @@ -package fr.xephi.authme.task; - -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboPlayer; -import fr.xephi.authme.data.limbo.LimboService; -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.RegistrationSettings; -import fr.xephi.authme.settings.properties.RestrictionSettings; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - -import javax.inject.Inject; - -import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; - -/** - * Registers tasks associated with a LimboPlayer. - */ -public class LimboPlayerTaskManager { - - @Inject - private Messages messages; - - @Inject - private Settings settings; - - @Inject - private BukkitService bukkitService; - - @Inject - private LimboService limboService; - - @Inject - private PlayerCache playerCache; - - LimboPlayerTaskManager() { - } - - - /** - * Registers a {@link MessageTask} for the given player name. - * - * @param name the name of the player to schedule a repeating message task for - * @param isRegistered whether the name is registered or not - * (false shows "please register", true shows "please log in") - */ - public void registerMessageTask(String name, boolean isRegistered) { - final int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL); - final MessageKey key = getMessageKey(isRegistered); - if (interval > 0) { - final LimboPlayer limboPlayer = limboService.getLimboPlayer(name); - if (limboPlayer == null) { - ConsoleLogger.info("LimboPlayer for '" + name + "' is not available (MessageTask)"); - } else { - MessageTask messageTask = new MessageTask(name, messages.retrieve(key), bukkitService, playerCache); - bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND); - limboPlayer.setMessageTask(messageTask); - } - } - } - - /** - * Registers a {@link TimeoutTask} for the given player according to the configuration. - * - * @param player the player to register a timeout task for - */ - public void registerTimeoutTask(Player player) { - final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; - if (timeout > 0) { - final LimboPlayer limboPlayer = limboService.getLimboPlayer(player.getName()); - if (limboPlayer == null) { - ConsoleLogger.info("LimboPlayer for '" + player.getName() + "' is not available (TimeoutTask)"); - } else { - String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); - BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout); - limboPlayer.setTimeoutTask(task); - } - } - } - - public void muteMessageTask(Player player) { - LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); - if (limbo != null) { - setMuted(limbo.getMessageTask(), true); - } - } - - public void unmuteMessageTask(Player player) { - LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); - if (limbo != null) { - setMuted(limbo.getMessageTask(), false); - } - } - - public void clearTasks(Player player) { - LimboPlayer limbo = limboService.getLimboPlayer(player.getName()); - if (limbo != null) { - limbo.clearTasks(); - } - } - - /** - * Returns the appropriate message key according to the registration status and settings. - * - * @param isRegistered whether or not the username is registered - * @return the message key to display to the user - */ - private MessageKey getMessageKey(boolean isRegistered) { - if (isRegistered) { - return MessageKey.LOGIN_MESSAGE; - } else { - return MessageKey.REGISTER_MESSAGE; - } - } - - /** - * Null-safe method to set the muted flag on a message task. - * - * @param task the task to modify (or null) - * @param isMuted the value to set if task is not null - */ - private static void setMuted(MessageTask task, boolean isMuted) { - if (task != null) { - task.setMuted(isMuted); - } - } -} diff --git a/src/main/java/fr/xephi/authme/task/MessageTask.java b/src/main/java/fr/xephi/authme/task/MessageTask.java index 74ef45f1a..cf4366d9d 100644 --- a/src/main/java/fr/xephi/authme/task/MessageTask.java +++ b/src/main/java/fr/xephi/authme/task/MessageTask.java @@ -1,7 +1,5 @@ package fr.xephi.authme.task; -import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.service.BukkitService; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; @@ -10,20 +8,16 @@ import org.bukkit.scheduler.BukkitRunnable; */ public class MessageTask extends BukkitRunnable { - private final String name; + private final Player player; private final String[] message; - private final BukkitService bukkitService; - private final PlayerCache playerCache; private boolean isMuted; /* * Constructor. */ - public MessageTask(String name, String[] lines, BukkitService bukkitService, PlayerCache playerCache) { - this.name = name; + public MessageTask(Player player, String[] lines) { + this.player = player; this.message = lines; - this.bukkitService = bukkitService; - this.playerCache = playerCache; isMuted = false; } @@ -33,21 +27,8 @@ public class MessageTask extends BukkitRunnable { @Override public void run() { - if (playerCache.isAuthenticated(name)) { - cancel(); - } - - if (isMuted) { - return; - } - - for (Player player : bukkitService.getOnlinePlayers()) { - if (player.getName().equalsIgnoreCase(name)) { - for (String ms : message) { - player.sendMessage(ms); - } - break; - } + if (!isMuted) { + player.sendMessage(message); } } } diff --git a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java index 5ba141e2c..bb331b286 100644 --- a/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/captcha/CaptchaCommandTest.java @@ -2,9 +2,9 @@ package fr.xephi.authme.command.executable.captcha; import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,7 +38,7 @@ public class CaptchaCommandTest { private CommonService commandService; @Mock - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Test public void shouldDetectIfPlayerIsLoggedIn() { @@ -90,7 +90,7 @@ public class CaptchaCommandTest { verifyNoMoreInteractions(captchaManager); verify(commandService).send(player, MessageKey.CAPTCHA_SUCCESS); verify(commandService).send(player, MessageKey.LOGIN_MESSAGE); - verify(limboPlayerTaskManager).unmuteMessageTask(player); + verify(limboService).unmuteMessageTask(player); verifyNoMoreInteractions(commandService); } diff --git a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java similarity index 72% rename from src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java rename to src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java index 0d3d8f715..8d3b6e85e 100644 --- a/src/test/java/fr/xephi/authme/task/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java @@ -1,15 +1,15 @@ -package fr.xephi.authme.task; +package fr.xephi.authme.data.limbo; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerCache; -import fr.xephi.authme.data.limbo.LimboPlayer; -import fr.xephi.authme.data.limbo.LimboService; 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.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import fr.xephi.authme.task.MessageTask; +import fr.xephi.authme.task.TimeoutTask; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; import org.junit.BeforeClass; @@ -51,9 +51,6 @@ public class LimboPlayerTaskManagerTest { @Mock private BukkitService bukkitService; - @Mock - private LimboService limboService; - @Mock private PlayerCache playerCache; @@ -65,16 +62,15 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldRegisterMessageTask() { // given - String name = "bobby"; + Player player = mock(Player.class); LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); MessageKey key = MessageKey.REGISTER_MESSAGE; given(messages.retrieve(key)).willReturn(new String[]{"Please register!"}); int interval = 12; given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(interval); // when - limboPlayerTaskManager.registerMessageTask(name, false); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false); // then verify(limboPlayer).setMessageTask(any(MessageTask.class)); @@ -83,32 +79,16 @@ public class LimboPlayerTaskManagerTest { any(MessageTask.class), eq(2L * TICKS_PER_SECOND), eq((long) interval * TICKS_PER_SECOND)); } - @Test - public void shouldNotScheduleTaskForMissingLimboPlayer() { - // given - String name = "ghost"; - given(limboService.getLimboPlayer(name)).willReturn(null); - given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(5); - - // when - limboPlayerTaskManager.registerMessageTask(name, true); - - // then - verify(limboService).getLimboPlayer(name); - verifyZeroInteractions(bukkitService); - verifyZeroInteractions(messages); - } - @Test public void shouldNotScheduleTaskForZeroAsInterval() { // given - String name = "Tester1"; + Player player = mock(Player.class); LimboPlayer limboPlayer = mock(LimboPlayer.class); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(0); // when - limboPlayerTaskManager.registerMessageTask(name, true); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, true); // then verifyZeroInteractions(limboPlayer, bukkitService); @@ -117,16 +97,14 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldCancelExistingMessageTask() { // given + Player player = mock(Player.class); LimboPlayer limboPlayer = new LimboPlayer(null, true, "grp", false, 0.1f, 0.0f); MessageTask existingMessageTask = mock(MessageTask.class); limboPlayer.setMessageTask(existingMessageTask); - - String name = "bobby"; - given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); // when - limboPlayerTaskManager.registerMessageTask(name, false); + limboPlayerTaskManager.registerMessageTask(player, limboPlayer, false); // then assertThat(limboPlayer.getMessageTask(), not(nullValue())); @@ -138,17 +116,14 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldRegisterTimeoutTask() { // given - String name = "l33tPlayer"; Player player = mock(Player.class); - given(player.getName()).willReturn(name); LimboPlayer limboPlayer = mock(LimboPlayer.class); - given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(30); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verify(limboPlayer).setTimeoutTask(bukkitTask); @@ -156,22 +131,6 @@ public class LimboPlayerTaskManagerTest { verify(messages).retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR); } - @Test - public void shouldNotRegisterTimeoutTaskForMissingLimboPlayer() { - // given - String name = "Phantom_"; - Player player = mock(Player.class); - given(player.getName()).willReturn(name); - given(limboService.getLimboPlayer(name)).willReturn(null); - given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(27); - - // when - limboPlayerTaskManager.registerTimeoutTask(player); - - // then - verifyZeroInteractions(bukkitService, messages); - } - @Test public void shouldNotRegisterTimeoutTaskForZeroTimeout() { // given @@ -180,7 +139,7 @@ public class LimboPlayerTaskManagerTest { given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(0); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verifyZeroInteractions(limboPlayer, bukkitService); @@ -189,19 +148,16 @@ public class LimboPlayerTaskManagerTest { @Test public void shouldCancelExistingTimeoutTask() { // given - String name = "l33tPlayer"; Player player = mock(Player.class); - given(player.getName()).willReturn(name); LimboPlayer limboPlayer = new LimboPlayer(null, false, "", true, 0.3f, 0.1f); BukkitTask existingTask = mock(BukkitTask.class); limboPlayer.setTimeoutTask(existingTask); - given(limboService.getLimboPlayer(name)).willReturn(limboPlayer); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(18); BukkitTask bukkitTask = mock(BukkitTask.class); given(bukkitService.runTaskLater(any(TimeoutTask.class), anyLong())).willReturn(bukkitTask); // when - limboPlayerTaskManager.registerTimeoutTask(player); + limboPlayerTaskManager.registerTimeoutTask(player, limboPlayer); // then verify(existingTask).cancel(); 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 12d4fee7a..2caaaef76 100644 --- a/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java +++ b/src/test/java/fr/xephi/authme/process/login/AsynchronousLoginTest.java @@ -3,18 +3,18 @@ package fr.xephi.authme.process.login; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; 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; import fr.xephi.authme.settings.properties.RestrictionSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.entity.Player; import org.junit.BeforeClass; import org.junit.Test; @@ -58,7 +58,7 @@ public class AsynchronousLoginTest { @Mock private CommonService commonService; @Mock - private LimboPlayerTaskManager limboPlayerTaskManager; + private LimboService limboService; @Mock private BukkitService bukkitService; @Mock diff --git a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java index 195de52c8..26433a45d 100644 --- a/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java +++ b/src/test/java/fr/xephi/authme/process/unregister/AsynchronousUnregisterTest.java @@ -14,7 +14,6 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.properties.RegistrationSettings; -import fr.xephi.authme.task.LimboPlayerTaskManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.BeforeClass; @@ -54,8 +53,6 @@ public class AsynchronousUnregisterTest { @Mock private LimboService limboService; @Mock - private LimboPlayerTaskManager limboPlayerTaskManager; - @Mock private TeleportationService teleportationService; @Mock private AuthGroupHandler authGroupHandler; @@ -84,7 +81,7 @@ public class AsynchronousUnregisterTest { // then verify(service).send(player, MessageKey.WRONG_PASSWORD); verify(passwordSecurity).comparePassword(userPassword, password, name); - verifyZeroInteractions(dataSource, limboPlayerTaskManager, limboService, authGroupHandler, teleportationService); + verifyZeroInteractions(dataSource, limboService, authGroupHandler, teleportationService); verify(player, only()).getName(); } @@ -170,7 +167,7 @@ public class AsynchronousUnregisterTest { verify(dataSource).removeAuth(name); verify(playerCache).removePlayer(name); verify(authGroupHandler).setGroup(player, AuthGroupType.UNREGISTERED); - verifyZeroInteractions(teleportationService, limboPlayerTaskManager); + verifyZeroInteractions(teleportationService, limboService); verify(bukkitService, never()).runTask(any(Runnable.class)); } From 7eadb7f7f989c4d89925c6e422665effdb6c0357 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 7 Mar 2017 22:08:04 +0100 Subject: [PATCH 069/125] #1034 Add debug sections for viewing DB data and Limbo data --- .../executable/authme/debug/DebugCommand.java | 2 +- .../authme/debug/DebugSectionUtils.java | 55 +++++++ .../authme/debug/LimboPlayerViewer.java | 147 ++++++++++++++++++ .../authme/debug/PlayerAuthViewer.java | 104 +++++++++++++ .../authme/debug/TestEmailSender.java | 7 +- .../authme/debug/DebugSectionUtilsTest.java | 46 ++++++ 6 files changed, 357 insertions(+), 4 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.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 c910cb929..ec4cfd830 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, PlayerAuthViewer.class, LimboPlayerViewer.class); private Map sections; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java new file mode 100644 index 000000000..e2861ca4f --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java @@ -0,0 +1,55 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import org.bukkit.Location; + +import java.math.RoundingMode; +import java.text.DecimalFormat; + +/** + * Utilities used within the DebugSection implementations. + */ +final class DebugSectionUtils { + + private DebugSectionUtils() { + } + + /** + * Formats the given location in a human readable way. Null-safe. + * + * @param location the location to format + * @return the formatted location + */ + static String formatLocation(Location location) { + if (location == null) { + return "null"; + } + + String worldName = location.getWorld() == null ? "null" : location.getWorld().getName(); + return formatLocation(location.getX(), location.getY(), location.getZ(), worldName); + } + + /** + * Formats the given location in a human readable way. + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param world the world name + * @return the formatted location + */ + static String formatLocation(double x, double y, double z, String world) { + return "(" + round(x) + ", " + round(y) + ", " + round(z) + ") in '" + world + "'"; + } + + /** + * Rounds the given number to two decimals. + * + * @param number the number to round + * @return the rounded number + */ + private static String round(double number) { + DecimalFormat df = new DecimalFormat("#.##"); + df.setRoundingMode(RoundingMode.HALF_UP); + return df.format(number); + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java new file mode 100644 index 000000000..5ac00fb18 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -0,0 +1,147 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; + +/** + * Shows the data stored in LimboPlayers and the equivalent properties on online players. + */ +class LimboPlayerViewer implements DebugSection { + + @Inject + private LimboService limboService; + + @Inject + private BukkitService bukkitService; + + private Field limboServiceEntries; + + @Override + public String getName() { + return "limbo"; + } + + @Override + public String getDescription() { + return "View LimboPlayers and player's \"limbo stats\""; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("/authme debug limbo : show a player's limbo info"); + sender.sendMessage("Available limbo records: " + getLimboKeys()); + return; + } + + LimboPlayer limbo = limboService.getLimboPlayer(arguments.get(0)); + Player player = bukkitService.getPlayerExact(arguments.get(0)); + if (limbo == null && player == null) { + sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'"); + return; + } + + sender.sendMessage(ChatColor.GOLD + "Showing limbo / player info for '" + arguments.get(0) + "'"); + new InfoDisplayer(sender, limbo, player) + .sendEntry("Is op", LimboPlayer::isOperator, Player::isOp) + .sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed) + .sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight) + .sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed) + .sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation())) + .sendEntry("Group", LimboPlayer::getGroup, p -> ""); + sender.sendMessage("Note: group is only shown for LimboPlayer"); + } + + /** + * Gets the names of the LimboPlayers in the LimboService. As we don't want to expose this + * information in non-debug settings, this is done over reflections. Since this is not a + * crucial feature, we generously catch all Exceptions + * + * @return player names for which there is a LimboPlayer (or error message upon failure) + */ + @SuppressWarnings("unchecked") + private Set getLimboKeys() { + // Lazy initialization + if (limboServiceEntries == null) { + try { + Field limboServiceEntries = LimboService.class.getDeclaredField("entries"); + limboServiceEntries.setAccessible(true); + this.limboServiceEntries = limboServiceEntries; + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService entries field:", e); + return Collections.singleton("Error retrieving LimboPlayer collection"); + } + } + + try { + return (Set) ((Map) limboServiceEntries.get(limboService)).keySet(); + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService values:", e); + return Collections.singleton("Error retrieving LimboPlayer values"); + } + } + + /** + * Displays the info for the given LimboPlayer and Player to the provided CommandSender. + */ + private static final class InfoDisplayer { + private final CommandSender sender; + private final Optional limbo; + private final Optional player; + + /** + * Constructor. + * + * @param sender command sender to send the information to + * @param limbo the limbo player to get data from + * @param player the player to get data from + */ + InfoDisplayer(CommandSender sender, LimboPlayer limbo, Player player) { + this.sender = sender; + this.limbo = Optional.ofNullable(limbo); + this.player = Optional.ofNullable(player); + + if (limbo == null) { + sender.sendMessage("Note: no Limbo information available"); + } else if (player == null) { + sender.sendMessage("Note: player is not online"); + } + } + + /** + * Displays a piece of information to the command sender. + * + * @param title the designation of the piece of information + * @param limboGetter getter for data retrieval on the LimboPlayer + * @param playerGetter getter for data retrieval on Player + * @param the data type + * @return this instance (for chaining) + */ + InfoDisplayer sendEntry(String title, + Function limboGetter, + Function playerGetter) { + sender.sendMessage( + title + ": " + + limbo.map(limboGetter).map(String::valueOf).orElse("--") + + " / " + + player.map(playerGetter).map(String::valueOf).orElse("--")); + return this; + } + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java new file mode 100644 index 000000000..a985c8271 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java @@ -0,0 +1,104 @@ +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.security.crypts.HashedPassword; +import fr.xephi.authme.util.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; + +/** + * Allows to view the data of a PlayerAuth in the database. + */ +class PlayerAuthViewer implements DebugSection { + + @Inject + private DataSource dataSource; + + @Override + public String getName() { + return "db"; + } + + @Override + public String getDescription() { + return "View player's data in the database"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + sender.sendMessage("Enter player name to view his data in the database."); + sender.sendMessage("Example: /authme debug db Bobby"); + return; + } + + PlayerAuth auth = dataSource.getAuth(arguments.get(0)); + if (auth == null) { + sender.sendMessage("No record exists for '" + arguments.get(0) + "'"); + } else { + displayAuthToSender(auth, sender); + } + } + + /** + * Outputs the PlayerAuth information to the given sender. + * + * @param auth the PlayerAuth to display + * @param sender the sender to send the messages to + */ + private void displayAuthToSender(PlayerAuth auth, CommandSender sender) { + sender.sendMessage(ChatColor.GOLD + "[AuthMe] Player " + auth.getNickname() + " / " + auth.getRealName()); + sender.sendMessage("Email: " + auth.getEmail() + ". IP: " + auth.getIp() + ". Group: " + auth.getGroupId()); + sender.sendMessage("Quit location: " + + formatLocation(auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getWorld())); + sender.sendMessage("Last login: " + formatLastLogin(auth)); + + HashedPassword hashedPass = auth.getPassword(); + sender.sendMessage("Hash / salt (partial): '" + safeSubstring(hashedPass.getHash(), 6) + + "' / '" + safeSubstring(hashedPass.getSalt(), 4) + "'"); + } + + /** + * Fail-safe substring method. Guarantees not to show the entire String. + * + * @param str the string to transform + * @param length number of characters to show from the start of the String + * @return the first length characters of the string, or half of the string if it is shorter, + * or empty string if the string is null or empty + */ + private static String safeSubstring(String str, int length) { + if (StringUtils.isEmpty(str)) { + return ""; + } else if (str.length() < length) { + return str.substring(0, str.length() / 2) + "..."; + } else { + return str.substring(0, length) + "..."; + } + } + + /** + * Formats the last login date from the given PlayerAuth. + * + * @param auth the auth object + * @return the last login as human readable date + */ + private static String formatLastLogin(PlayerAuth auth) { + long lastLogin = auth.getLastLogin(); + if (lastLogin == 0) { + return "Never (0)"; + } else { + LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastLogin), ZoneId.systemDefault()); + return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date); + } + } +} 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 b0abcd557..1d5052548 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 @@ -41,8 +41,8 @@ class TestEmailSender implements DebugSection { @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"); + sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml " + + "for sending emails. Please check your config.yml"); return; } @@ -69,7 +69,8 @@ class TestEmailSender implements DebugSection { } 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 "); + sender.sendMessage(ChatColor.RED + "No email set for your account!" + + " Please use /authme debug mail "); return null; } return email; diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java new file mode 100644 index 000000000..caf617883 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java @@ -0,0 +1,46 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.TestHelper; +import org.bukkit.Location; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link DebugSectionUtils}. + */ +public class DebugSectionUtilsTest { + + @Test + public void shouldFormatLocation() { + // given / when + String result = DebugSectionUtils.formatLocation(0.0, 10.248592, -18934.2349023, "Main"); + + // then + assertThat(result, equalTo("(0, 10.25, -18934.23) in 'Main'")); + } + + @Test + public void shouldHandleNullWorld() { + // given + Location location = new Location(null, 3.7777, 2.14156, 1); + + // when + String result = DebugSectionUtils.formatLocation(location); + + // then + assertThat(result, equalTo("(3.78, 2.14, 1) in 'null'")); + } + + @Test + public void shouldHandleNullLocation() { + // given / when / then + assertThat(DebugSectionUtils.formatLocation(null), equalTo("null")); + } + + @Test + public void shouldHaveHiddenConstructor() { + TestHelper.validateHasOnlyPrivateEmptyConstructor(DebugSectionUtils.class); + } +} From 5ca1c17771571c338c6f806e00a3e031736b609e Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 8 Mar 2017 01:21:29 +0200 Subject: [PATCH 070/125] 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 9dc842535..2ca1df559 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 071/125] 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 38e79face..ab3bdeacb 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 072/125] 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 ab3bdeacb..a39a9d536 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 073/125] #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 7c7abf785..691e38617 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 074/125] #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 691e38617..dd1f6e51e 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 075/125] 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 ac7415718..5ee7a5646 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 076/125] #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 000000000..05de8ab13 --- /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 c910cb929..510ac9a1a 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 c79ba49ca88e6835afa9299581d87ef795aa7242 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 11 Mar 2017 08:47:58 +0100 Subject: [PATCH 077/125] #1113 Don't restore OP to unregistered player; add tests for LimboService --- .../xephi/authme/data/limbo/LimboService.java | 8 +- .../authme/process/join/AsynchronousJoin.java | 1 - .../authme/data/limbo/LimboServiceTest.java | 224 ++++++++++++++++++ 3 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index 90b96d378..775337a83 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -105,7 +105,7 @@ public class LimboService { ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name); } - LimboPlayer limboPlayer = newLimboPlayer(player); + LimboPlayer limboPlayer = newLimboPlayer(player, isRegistered); taskManager.registerMessageTask(player, limboPlayer, isRegistered); taskManager.registerTimeoutTask(player, limboPlayer); revokeLimboStates(player); @@ -157,11 +157,13 @@ public class LimboService { * Creates a LimboPlayer with the given player's details. * * @param player the player to process + * @param isRegistered whether the player is registered * @return limbo player with the player's data */ - private LimboPlayer newLimboPlayer(Player player) { + private LimboPlayer newLimboPlayer(Player player, boolean isRegistered) { Location location = spawnLoader.getPlayerLocationOrSpawn(player); - boolean isOperator = player.isOp(); + // For safety reasons an unregistered player should not have OP status after registration + boolean isOperator = isRegistered && player.isOp(); boolean flyEnabled = player.getAllowFlight(); float walkSpeed = player.getWalkSpeed(); float flySpeed = player.getFlySpeed(); 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 20c2ddd49..eed9933f8 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -148,7 +148,6 @@ public class AsynchronousJoin implements AsynchronousProcess { final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND; bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> { - // TODO #1113: Find an elegant way to deop unregistered players (and disable fly status etc.?) limboService.createLimboPlayer(player, isAuthAvailable); player.setNoDamageTicks(registrationTimeout); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java new file mode 100644 index 000000000..69d3c3704 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -0,0 +1,224 @@ +package fr.xephi.authme.data.limbo; + +import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Map; + +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +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 LimboService}. + */ +@RunWith(MockitoJUnitRunner.class) +public class LimboServiceTest { + + @InjectMocks + private LimboService limboService; + + @Mock + private SpawnLoader spawnLoader; + + @Mock + private PermissionsManager permissionsManager; + + @Mock + private Settings settings; + + @Mock + private LimboPlayerTaskManager taskManager; + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @Before + public void mockSettings() { + given(settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)).willReturn(false); + given(settings.getProperty(RestrictionSettings.REMOVE_SPEED)).willReturn(true); + } + + @Test + public void shouldCreateLimboPlayer() { + // given + Player player = newPlayer("Bobby", true, 0.3f, false, 0.2f); + Location playerLoc = mock(Location.class); + given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); + given(permissionsManager.hasGroupSupport()).willReturn(true); + given(permissionsManager.getPrimaryGroup(player)).willReturn("permgrwp"); + + // when + limboService.createLimboPlayer(player, true); + + // then + verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(true)); + verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class)); + verify(player).setAllowFlight(false); + verify(player).setFlySpeed(0.0f); + verify(player).setWalkSpeed(0.0f); + + LimboPlayer limbo = getLimboMap().get("bobby"); + assertThat(limbo, not(nullValue())); + assertThat(limbo.isOperator(), equalTo(true)); + assertThat(limbo.getWalkSpeed(), equalTo(0.3f)); + assertThat(limbo.isCanFly(), equalTo(false)); + assertThat(limbo.getFlySpeed(), equalTo(0.2f)); + assertThat(limbo.getLocation(), equalTo(playerLoc)); + assertThat(limbo.getGroup(), equalTo("permgrwp")); + } + + @Test + public void shouldNotKeepOpStatusForUnregisteredPlayer() { + // given + Player player = newPlayer("CharleS", true, 0.1f, true, 0.4f); + Location playerLoc = mock(Location.class); + given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); + given(permissionsManager.hasGroupSupport()).willReturn(false); + + // when + limboService.createLimboPlayer(player, false); + + // then + verify(taskManager).registerMessageTask(eq(player), any(LimboPlayer.class), eq(false)); + verify(taskManager).registerTimeoutTask(eq(player), any(LimboPlayer.class)); + verify(permissionsManager, only()).hasGroupSupport(); + verify(player).setAllowFlight(false); + verify(player).setFlySpeed(0.0f); + verify(player).setWalkSpeed(0.0f); + + LimboPlayer limbo = getLimboMap().get("charles"); + assertThat(limbo, not(nullValue())); + assertThat(limbo.isOperator(), equalTo(false)); + assertThat(limbo.getWalkSpeed(), equalTo(0.1f)); + assertThat(limbo.isCanFly(), equalTo(true)); + assertThat(limbo.getFlySpeed(), equalTo(0.4f)); + assertThat(limbo.getLocation(), equalTo(playerLoc)); + assertThat(limbo.getGroup(), equalTo("")); + } + + @Test + public void shouldClearTasksOnAlreadyExistingLimbo() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + getLimboMap().put("carlos", limbo); + Player player = newPlayer("Carlos"); + + // when + limboService.createLimboPlayer(player, false); + + // then + verify(limbo).clearTasks(); + assertThat(getLimboMap().get("carlos"), both(not(sameInstance(limbo))).and(not(nullValue()))); + } + + @Test + public void shouldRestoreData() { + // given + Player player = newPlayer("John", true, 0.4f, false, 0.2f); + LimboPlayer limbo = Mockito.spy(convertToLimboPlayer(player, null, "")); + getLimboMap().put("john", limbo); + + // when + limboService.restoreData(player); + + // then + verify(player).setOp(true); + verify(player).setWalkSpeed(0.4f); + verify(player).setAllowFlight(false); + verify(player).setFlySpeed(0.2f); + verify(limbo).clearTasks(); + assertThat(getLimboMap(), anEmptyMap()); + } + + @Test + public void shouldHandleMissingLimboPlayerWhileRestoring() { + // given + Player player = newPlayer("Test"); + + // when + limboService.restoreData(player); + + // then + verify(player, only()).getName(); + } + + @Test + public void shouldReplaceTasks() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + getLimboMap().put("jeff", limbo); + Player player = newPlayer("JEFF"); + + + // when + limboService.replaceTasksAfterRegistration(player); + + // then + verify(taskManager).registerTimeoutTask(player, limbo); + verify(taskManager).registerMessageTask(player, limbo, true); + } + + @Test + public void shouldHandleMissingLimboForReplaceTasks() { + // given + Player player = newPlayer("ghost"); + + // when + limboService.replaceTasksAfterRegistration(player); + + // then + verifyZeroInteractions(taskManager); + } + + private static Player newPlayer(String name) { + Player player = mock(Player.class); + given(player.getName()).willReturn(name); + return player; + } + + private static Player newPlayer(String name, boolean isOp, float walkSpeed, boolean canFly, float flySpeed) { + Player player = newPlayer(name); + given(player.isOp()).willReturn(isOp); + given(player.getWalkSpeed()).willReturn(walkSpeed); + given(player.getAllowFlight()).willReturn(canFly); + given(player.getFlySpeed()).willReturn(flySpeed); + return player; + } + + private static LimboPlayer convertToLimboPlayer(Player player, Location location, String group) { + return new LimboPlayer(location, player.isOp(), group, player.getAllowFlight(), + player.getWalkSpeed(), player.getFlySpeed()); + } + + private Map getLimboMap() { + return ReflectionTestUtils.getFieldValue(LimboService.class, limboService, "entries"); + } +} From 72acf2823385114ab873bb58c2ca8a6b8f128149 Mon Sep 17 00:00:00 2001 From: rafael59r2 Date: Sun, 12 Mar 2017 06:14:02 +0000 Subject: [PATCH 078/125] 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 000000000..2b2ead6c3 --- /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 c766b5c25936da3dc7d15b07be5ddc7564e8b6d4 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 12 Mar 2017 13:51:03 +0100 Subject: [PATCH 079/125] #1036 Add restoration options for Limbo allowFlight / fly speed / walk speed - Introduce options to define how allow flight, fly & walk speed should be restored from LimboPlayer - Create consistency tests for line length in SectionComments methods and to ensure that all SettingsHolder classes are part of the returned ConfigurationData --- .checkstyle.xml | 2 +- docs/config.md | 45 +++++--- .../data/limbo/AllowFlightRestoreType.java | 43 +++++++ .../xephi/authme/data/limbo/LimboService.java | 19 ++- .../data/limbo/WalkFlySpeedRestoreType.java | 81 +++++++++++++ .../properties/AuthMeSettingsRetriever.java | 8 +- .../settings/properties/LimboSettings.java | 57 +++++++++ .../fr/xephi/authme/ReflectionTestUtils.java | 5 +- .../limbo/AllowFlightRestoreTypeTest.java | 72 ++++++++++++ .../authme/data/limbo/LimboServiceTest.java | 33 +++--- .../limbo/WalkFlySpeedRestoreTypeTest.java | 108 ++++++++++++++++++ .../settings/SettingsConsistencyTest.java | 66 +++++++++++ .../SettingsClassConsistencyTest.java | 24 ++++ 13 files changed, 518 insertions(+), 45 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java create mode 100644 src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java diff --git a/.checkstyle.xml b/.checkstyle.xml index d8f9076d1..4ff5d8dfb 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -145,7 +145,7 @@ - + diff --git a/docs/config.md b/docs/config.md index ff2d54fb4..3e750b2d8 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, @@ -73,17 +73,6 @@ ExternalBoardOptions: phpbbActivatedGroupId: 2 # Wordpress prefix defined during WordPress installation wordpressTablePrefix: 'wp_' -Converter: - Rakamak: - # Rakamak file name - fileName: 'users.rak' - # Rakamak use IP? - useIP: false - # Rakamak IP file name - ipFileName: 'UsersIp.rak' - CrazyLogin: - # CrazyLogin database file name - fileName: 'accounts.db' settings: sessions: # Do you want to enable the session feature? @@ -323,6 +312,8 @@ Email: mailSMTP: 'smtp.gmail.com' # Email SMTP server port mailPort: 465 + # Only affects port 25: enable TLS/STARTTLS? + useTls: true # Email account which sends the mails mailAccount: '' # Email account password @@ -444,6 +435,23 @@ Security: # 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 +# Before a user logs in, various properties are temporarily removed from the player, +# such as OP status, ability to fly, and walk/fly speed. +# Once the user is logged in, we add back the properties we previously saved. +# In this section, you may define how the properties should be restored. +limbo: + # Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE. + # RESTORE sets back the old property from the player. + restoreAllowFlight: 'RESTORE' + # Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO. + # RESTORE: restore the speed the player had; + # DEFAULT: always set to default speed; + # MAX_RESTORE: take the maximum of the player's current speed and the previous one + # RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0 + restoreFlySpeed: 'MAX_RESTORE' + # Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO. + # See above for a description of the values. + restoreWalkSpeed: 'MAX_RESTORE' BackupSystem: # Enable or disable automatic backup ActivateBackup: false @@ -453,6 +461,17 @@ BackupSystem: OnServerStop: true # Windows only mysql installation Path MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\' +Converter: + Rakamak: + # Rakamak file name + fileName: 'users.rak' + # Rakamak use IP? + useIP: false + # Rakamak IP file name + ipFileName: 'UsersIp.rak' + CrazyLogin: + # CrazyLogin database file name + fileName: 'accounts.db' ``` To change settings on a running server, save your changes to config.yml and use @@ -460,4 +479,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 Feb 25 21:59:18 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Mar 12 13:39:50 CET 2017 diff --git a/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java new file mode 100644 index 000000000..f085afbb4 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; + +import java.util.function.Function; + +/** + * Possible types to restore the "allow flight" property + * from LimboPlayer to Bukkit Player. + */ +public enum AllowFlightRestoreType { + + /** Set value from LimboPlayer to Player. */ + RESTORE(LimboPlayer::isCanFly), + + /** Always set flight enabled to true. */ + ENABLE(l -> true), + + /** Always set flight enabled to false. */ + DISABLE(l -> false); + + private final Function valueGetter; + + /** + * Constructor. + * + * @param valueGetter function with which the value to set on the player can be retrieved + */ + AllowFlightRestoreType(Function valueGetter) { + this.valueGetter = valueGetter; + } + + /** + * Restores the "allow flight" property from the LimboPlayer to the Player. + * This method behaves differently for each restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public void restoreAllowFlight(Player player, LimboPlayer limbo) { + player.setAllowFlight(valueGetter.apply(limbo)); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index 775337a83..b0c93d7d1 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -13,6 +13,10 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_ALLOW_FLIGHT; +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_FLY_SPEED; +import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_WALK_SPEED; + /** * Service for managing players that are in "limbo," a temporary state players are * put in which have joined but not yet logged in yet. @@ -53,18 +57,9 @@ public class LimboService { ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName); } else { player.setOp(limbo.isOperator()); - player.setAllowFlight(limbo.isCanFly()); - float walkSpeed = limbo.getWalkSpeed(); - float flySpeed = limbo.getFlySpeed(); - // Reset the speed value if it was 0 - if (walkSpeed < 0.01f) { - walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - } - if (flySpeed < 0.01f) { - flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - } - player.setWalkSpeed(walkSpeed); - player.setFlySpeed(flySpeed); + settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo); + settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo); + settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo); limbo.clearTasks(); ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName); } diff --git a/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java new file mode 100644 index 000000000..960cdd435 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java @@ -0,0 +1,81 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; + +/** + * Possible types to restore the walk and fly speed from LimboPlayer + * back to Bukkit Player. + */ +public enum WalkFlySpeedRestoreType { + + /** Restores from LimboPlayer to Player. */ + RESTORE { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(limbo.getFlySpeed()); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(limbo.getWalkSpeed()); + } + }, + + /** Restores from LimboPlayer, using the default speed if the speed on LimboPlayer is 0. */ + RESTORE_NO_ZERO { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + float limboFlySpeed = limbo.getFlySpeed(); + player.setFlySpeed(limboFlySpeed > 0.01f ? limboFlySpeed : LimboPlayer.DEFAULT_FLY_SPEED); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + float limboWalkSpeed = limbo.getWalkSpeed(); + player.setWalkSpeed(limboWalkSpeed > 0.01f ? limboWalkSpeed : LimboPlayer.DEFAULT_WALK_SPEED); + } + }, + + /** Uses the max speed of Player (current speed) and the LimboPlayer. */ + MAX_RESTORE { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(Math.max(player.getFlySpeed(), limbo.getFlySpeed())); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(Math.max(player.getWalkSpeed(), limbo.getWalkSpeed())); + } + }, + + /** Always sets the default speed to the player. */ + DEFAULT { + @Override + public void restoreFlySpeed(Player player, LimboPlayer limbo) { + player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); + } + + @Override + public void restoreWalkSpeed(Player player, LimboPlayer limbo) { + player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED); + } + }; + + /** + * Restores the fly speed from Limbo to Player according to the restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public abstract void restoreFlySpeed(Player player, LimboPlayer limbo); + + /** + * Restores the walk speed from Limbo to Player according to the restoration type. + * + * @param player the player to modify + * @param limbo the limbo player to read from + */ + public abstract void restoreWalkSpeed(Player player, LimboPlayer limbo); + +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java b/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java index 8cfb29dc4..2fbe7ea24 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java +++ b/src/main/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetriever.java @@ -20,9 +20,9 @@ public final class AuthMeSettingsRetriever { */ public static ConfigurationData buildConfigurationData() { return ConfigurationDataBuilder.collectData( - DatabaseSettings.class, ConverterSettings.class, PluginSettings.class, - RestrictionSettings.class, EmailSettings.class, HooksSettings.class, - ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class, - RegistrationSettings.class, BackupSettings.class); + DatabaseSettings.class, PluginSettings.class, RestrictionSettings.class, + EmailSettings.class, HooksSettings.class, ProtectionSettings.class, + PurgeSettings.class, SecuritySettings.class, RegistrationSettings.class, + LimboSettings.class, BackupSettings.class, ConverterSettings.class); } } diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java new file mode 100644 index 000000000..f861c4990 --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -0,0 +1,57 @@ +package fr.xephi.authme.settings.properties; + +import ch.jalu.configme.Comment; +import ch.jalu.configme.SectionComments; +import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.properties.Property; +import com.google.common.collect.ImmutableMap; +import fr.xephi.authme.data.limbo.AllowFlightRestoreType; +import fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType; + +import java.util.Map; + +import static ch.jalu.configme.properties.PropertyInitializer.newProperty; + +/** + * Settings for the LimboPlayer feature. + */ +public final class LimboSettings implements SettingsHolder { + + @Comment({ + "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.", + "RESTORE sets back the old property from the player." + }) + public static final Property RESTORE_ALLOW_FLIGHT = + newProperty(AllowFlightRestoreType.class, "limbo.restoreAllowFlight", AllowFlightRestoreType.RESTORE); + + @Comment({ + "Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.", + "RESTORE: restore the speed the player had;", + "DEFAULT: always set to default speed;", + "MAX_RESTORE: take the maximum of the player's current speed and the previous one", + "RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0" + }) + public static final Property RESTORE_FLY_SPEED = + newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreFlySpeed", WalkFlySpeedRestoreType.MAX_RESTORE); + + @Comment({ + "Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.", + "See above for a description of the values." + }) + public static final Property RESTORE_WALK_SPEED = + newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreWalkSpeed", WalkFlySpeedRestoreType.MAX_RESTORE); + + private LimboSettings() { + } + + @SectionComments + public static Map createSectionComments() { + String[] limboExplanation = { + "Before a user logs in, various properties are temporarily removed from the player,", + "such as OP status, ability to fly, and walk/fly speed.", + "Once the user is logged in, we add back the properties we previously saved.", + "In this section, you may define how the properties should be restored." + }; + return ImmutableMap.of("limbo", limboExplanation); + } +} diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index fdd716bd5..bc0876669 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -75,10 +75,11 @@ public final class ReflectionTestUtils { } } - public static Object invokeMethod(Method method, Object instance, Object... parameters) { + @SuppressWarnings("unchecked") + public static V invokeMethod(Method method, Object instance, Object... parameters) { method.setAccessible(true); try { - return method.invoke(instance, parameters); + return (V) method.invoke(instance, parameters); } catch (InvocationTargetException | IllegalAccessException e) { throw new UnsupportedOperationException("Could not invoke method '" + method + "'", e); } diff --git a/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java new file mode 100644 index 000000000..8c22cc756 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; +import org.junit.Test; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link AllowFlightRestoreType}. + */ +public class AllowFlightRestoreTypeTest { + + @Test + public void shouldRestoreValue() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.RESTORE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.RESTORE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(true); + verify(player2).setAllowFlight(false); + } + + @Test + public void shouldEnableFlight() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.ENABLE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.ENABLE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(true); + verify(player2).setAllowFlight(true); + } + + + @Test + public void shouldDisableFlight() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.DISABLE.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.DISABLE.restoreAllowFlight(player2, limboWithoutFly); + + // then + verify(player1).setAllowFlight(false); + verify(player2).setAllowFlight(false); + } + + private static LimboPlayer newLimboWithAllowFlight(boolean allowFlight) { + LimboPlayer limbo = mock(LimboPlayer.class); + given(limbo.isCanFly()).willReturn(allowFlight); + return limbo; + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java index 69d3c3704..4abe481ce 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -5,6 +5,7 @@ import fr.xephi.authme.TestHelper; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.LimboSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -19,8 +20,6 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Map; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -85,7 +84,8 @@ public class LimboServiceTest { verify(player).setFlySpeed(0.0f); verify(player).setWalkSpeed(0.0f); - LimboPlayer limbo = getLimboMap().get("bobby"); + assertThat(limboService.hasLimboPlayer("Bobby"), equalTo(true)); + LimboPlayer limbo = limboService.getLimboPlayer("Bobby"); assertThat(limbo, not(nullValue())); assertThat(limbo.isOperator(), equalTo(true)); assertThat(limbo.getWalkSpeed(), equalTo(0.3f)); @@ -114,7 +114,7 @@ public class LimboServiceTest { verify(player).setFlySpeed(0.0f); verify(player).setWalkSpeed(0.0f); - LimboPlayer limbo = getLimboMap().get("charles"); + LimboPlayer limbo = limboService.getLimboPlayer("charles"); assertThat(limbo, not(nullValue())); assertThat(limbo.isOperator(), equalTo(false)); assertThat(limbo.getWalkSpeed(), equalTo(0.1f)); @@ -127,24 +127,31 @@ public class LimboServiceTest { @Test public void shouldClearTasksOnAlreadyExistingLimbo() { // given - LimboPlayer limbo = mock(LimboPlayer.class); - getLimboMap().put("carlos", limbo); + LimboPlayer existingLimbo = mock(LimboPlayer.class); + getLimboMap().put("carlos", existingLimbo); Player player = newPlayer("Carlos"); // when limboService.createLimboPlayer(player, false); // then - verify(limbo).clearTasks(); - assertThat(getLimboMap().get("carlos"), both(not(sameInstance(limbo))).and(not(nullValue()))); + verify(existingLimbo).clearTasks(); + LimboPlayer newLimbo = limboService.getLimboPlayer("Carlos"); + assertThat(newLimbo, not(nullValue())); + assertThat(newLimbo, not(sameInstance(existingLimbo))); } @Test public void shouldRestoreData() { // given - Player player = newPlayer("John", true, 0.4f, false, 0.2f); - LimboPlayer limbo = Mockito.spy(convertToLimboPlayer(player, null, "")); + LimboPlayer limbo = Mockito.spy(convertToLimboPlayer( + newPlayer("John", true, 0.4f, false, 0.0f), null, "")); getLimboMap().put("john", limbo); + Player player = newPlayer("John", false, 0.2f, false, 0.7f); + + given(settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)).willReturn(AllowFlightRestoreType.ENABLE); + given(settings.getProperty(LimboSettings.RESTORE_WALK_SPEED)).willReturn(WalkFlySpeedRestoreType.RESTORE); + given(settings.getProperty(LimboSettings.RESTORE_FLY_SPEED)).willReturn(WalkFlySpeedRestoreType.RESTORE_NO_ZERO); // when limboService.restoreData(player); @@ -152,10 +159,10 @@ public class LimboServiceTest { // then verify(player).setOp(true); verify(player).setWalkSpeed(0.4f); - verify(player).setAllowFlight(false); - verify(player).setFlySpeed(0.2f); + verify(player).setAllowFlight(true); + verify(player).setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED); verify(limbo).clearTasks(); - assertThat(getLimboMap(), anEmptyMap()); + assertThat(limboService.hasLimboPlayer("John"), equalTo(false)); } @Test diff --git a/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java new file mode 100644 index 000000000..f8aa2719c --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreTypeTest.java @@ -0,0 +1,108 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static fr.xephi.authme.data.limbo.LimboPlayer.DEFAULT_FLY_SPEED; +import static fr.xephi.authme.data.limbo.LimboPlayer.DEFAULT_WALK_SPEED; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.DEFAULT; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.MAX_RESTORE; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.RESTORE; +import static fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType.RESTORE_NO_ZERO; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link WalkFlySpeedRestoreType}. + */ +@RunWith(Parameterized.class) +public class WalkFlySpeedRestoreTypeTest { + + private final TestParameters parameters; + + public WalkFlySpeedRestoreTypeTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void shouldRestoreToExpectedValue() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + given(limbo.getWalkSpeed()).willReturn(parameters.givenLimboWalkSpeed); + given(limbo.getFlySpeed()).willReturn(parameters.givenLimboFlySpeed); + + Player player = mock(Player.class); + given(player.getWalkSpeed()).willReturn(parameters.givenPlayerWalkSpeed); + given(player.getFlySpeed()).willReturn(parameters.givenPlayerFlySpeed); + + // when + parameters.testedType.restoreWalkSpeed(player, limbo); + parameters.testedType.restoreFlySpeed(player, limbo); + + // then + verify(player).setWalkSpeed(parameters.expectedWalkSpeed); + verify(player).setFlySpeed(parameters.expectedFlySpeed); + } + + @Parameterized.Parameters(name = "{0}") + public static List buildParams() { + List parameters = Arrays.asList( + create(RESTORE).withLimbo(0.1f, 0.4f).withPlayer(0.3f, 0.9f).expect(0.1f, 0.4f), + create(RESTORE).withLimbo(0.9f, 0.2f).withPlayer(0.3f, 0.0f).expect(0.9f, 0.2f), + create(MAX_RESTORE).withLimbo(0.3f, 0.8f).withPlayer(0.5f, 0.2f).expect(0.5f, 0.8f), + create(MAX_RESTORE).withLimbo(0.4f, 0.2f).withPlayer(0.1f, 0.4f).expect(0.4f, 0.4f), + create(RESTORE_NO_ZERO).withLimbo(0.1f, 0.2f).withPlayer(0.5f, 0.1f).expect(0.1f, 0.2f), + create(RESTORE_NO_ZERO).withLimbo(0.0f, 0.005f).withPlayer(0.4f, 0.8f).expect(DEFAULT_WALK_SPEED, DEFAULT_FLY_SPEED), + create(DEFAULT).withLimbo(0.1f, 0.7f).withPlayer(0.4f, 0.0f).expect(DEFAULT_WALK_SPEED, DEFAULT_FLY_SPEED) + ); + + // Convert List to List + return parameters.stream().map(p -> new Object[]{p}).collect(Collectors.toList()); + } + + private static TestParameters create(WalkFlySpeedRestoreType testedType) { + TestParameters params = new TestParameters(); + params.testedType = testedType; + return params; + } + + private static final class TestParameters { + private WalkFlySpeedRestoreType testedType; + private float givenLimboWalkSpeed; + private float givenLimboFlySpeed; + private float givenPlayerWalkSpeed; + private float givenPlayerFlySpeed; + private float expectedWalkSpeed; + private float expectedFlySpeed; + + TestParameters withLimbo(float walkSpeed, float flySpeed) { + this.givenLimboWalkSpeed = walkSpeed; + this.givenLimboFlySpeed = flySpeed; + return this; + } + + TestParameters withPlayer(float walkSpeed, float flySpeed) { + this.givenPlayerWalkSpeed = walkSpeed; + this.givenPlayerFlySpeed = flySpeed; + return this; + } + + TestParameters expect(float walkSpeed, float flySpeed) { + this.expectedWalkSpeed = walkSpeed; + this.expectedFlySpeed = flySpeed; + return this; + } + + @Override + public String toString() { + return testedType + " {" + expectedWalkSpeed + ", " + expectedFlySpeed + "}"; + } + } +} diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index 88724a847..7d6c53be6 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.SectionComments; +import ch.jalu.configme.SettingsHolder; import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.Property; +import fr.xephi.authme.ClassCollector; +import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; import org.junit.BeforeClass; import org.junit.Test; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +import static com.google.common.base.Preconditions.checkArgument; import static org.junit.Assert.fail; /** @@ -66,4 +76,60 @@ public class SettingsConsistencyTest { } } + @Test + public void shouldNotHaveVeryLongSectionCommentLines() { + // given + List sectionCommentMethods = getSectionCommentMethods(); + Set badMethods = new HashSet<>(); + + // when + for (Method method : sectionCommentMethods) { + boolean hasTooLongLine = getSectionComments(method).stream() + .anyMatch(line -> line.length() > MAX_COMMENT_LENGTH); + if (hasTooLongLine) { + badMethods.add(method); + } + } + + // then + if (!badMethods.isEmpty()) { + String methodList = badMethods.stream() + .map(m -> m.getName() + " in " + m.getDeclaringClass().getSimpleName()) + .collect(Collectors.joining("\n- ")); + fail("Found SectionComments methods with too long comments:\n- " + methodList); + } + } + + /** + * Gets all {@link SectionComments} methods from {@link SettingsHolder} implementations. + */ + @SuppressWarnings("unchecked") + private List getSectionCommentMethods() { + // Find all SettingsHolder classes + List> settingsClasses = + new ClassCollector("src/main/java", "fr/xephi/authme/settings/properties/") + .collectClasses(SettingsHolder.class); + checkArgument(!settingsClasses.isEmpty(), "Could not find any SettingsHolder classes"); + + // Find all @SectionComments methods in these classes + return settingsClasses.stream() + .map(Class::getDeclaredMethods) + .flatMap(Arrays::stream) + .filter(method -> method.isAnnotationPresent(SectionComments.class)) + .collect(Collectors.toList()); + } + + /** + * Returns all comments returned from the given SectionComments method, flattened into one list. + * + * @param sectionCommentsMethod the method whose comments should be retrieved + * @return flattened list of all comments provided by the method + */ + private static List getSectionComments(Method sectionCommentsMethod) { + // @SectionComments methods are static + Map comments = ReflectionTestUtils.invokeMethod(sectionCommentsMethod, null); + return comments.values().stream() + .flatMap(Arrays::stream) + .collect(Collectors.toList()); + } } diff --git a/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java index a88dbfb9a..d0d741359 100644 --- a/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/properties/SettingsClassConsistencyTest.java @@ -1,6 +1,7 @@ package fr.xephi.authme.settings.properties; import ch.jalu.configme.SettingsHolder; +import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.properties.Property; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.ReflectionTestUtils; @@ -10,11 +11,13 @@ import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -74,6 +77,27 @@ public class SettingsClassConsistencyTest { } } + /** + * Checks that {@link AuthMeSettingsRetriever} returns a ConfigurationData with all + * available SettingsHolder classes. + */ + @Test + public void shouldHaveAllClassesInConfigurationData() { + // given + long totalProperties = classes.stream() + .map(Class::getDeclaredFields) + .flatMap(Arrays::stream) + .filter(field -> Property.class.isAssignableFrom(field.getType())) + .count(); + + // when + ConfigurationData configData = AuthMeSettingsRetriever.buildConfigurationData(); + + // then + assertThat("ConfigurationData should have " + totalProperties + " properties (as found manually)", + configData.getProperties(), hasSize((int) totalProperties)); + } + @Test public void shouldHaveHiddenEmptyConstructorOnly() { for (Class clazz : classes) { From 10d8f00c9277770a5e1ada40a92ae53226aeb984 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 12 Mar 2017 14:04:39 +0100 Subject: [PATCH 080/125] 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 037846aaf..5a3604f1d 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 344c39c96..ece409005 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 6fb8ff856..255b36b01 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 cea51ea01..d2381a455 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 d87ebd5e3..f996640c9 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 fed077682..fe6d2fbf8 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 b7628147b..4d6983df5 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 000000000..18ab9fd22 --- /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 835d4b49b..74318498f 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 12d4fee7a..01b5e81cc 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 7927f826f..ad0b7e73f 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 d4e3f8cda..e07ea1c2e 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 458023d6d..cbb64f370 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 1678901e02f00414a6b480e6ecde9baeda476c1e Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 12 Mar 2017 15:56:08 +0100 Subject: [PATCH 081/125] #1113 Attempt to merge new LimboPlayer with an existing one - Extract some logic into LimboServiceHelper to keep LimboService slim - Create LimboServiceHelper#merge to merge two LimboPlayers associated with a Player. E.g. if an admin unregisters an online player that has not logged in, the creation of a LimboPlayer is triggered while there already is one in LimboService --- .../xephi/authme/data/limbo/LimboService.java | 142 ++++++------------ .../authme/data/limbo/LimboServiceHelper.java | 103 +++++++++++++ .../data/limbo/LimboServiceHelperTest.java | 82 ++++++++++ .../authme/data/limbo/LimboServiceTest.java | 13 +- 4 files changed, 240 insertions(+), 100 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index b0c93d7d1..290b79474 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -1,11 +1,7 @@ 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.RestrictionSettings; -import org.bukkit.Location; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -25,21 +21,61 @@ public class LimboService { private final Map entries = new ConcurrentHashMap<>(); - @Inject - private SpawnLoader spawnLoader; - - @Inject - private PermissionsManager permissionsManager; - @Inject private Settings settings; @Inject private LimboPlayerTaskManager taskManager; + @Inject + private LimboServiceHelper limboServiceHelper; + LimboService() { } + /** + * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player. + * + * @param player the player to process + * @param isRegistered whether or not the player is registered + */ + public void createLimboPlayer(Player player, boolean isRegistered) { + final String name = player.getName().toLowerCase(); + + LimboPlayer existingLimbo = entries.remove(name); + if (existingLimbo != null) { + existingLimbo.clearTasks(); + ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name); + } + + LimboPlayer limboPlayer = limboServiceHelper.merge( + limboServiceHelper.createLimboPlayer(player, isRegistered), existingLimbo); + + taskManager.registerMessageTask(player, limboPlayer, isRegistered); + taskManager.registerTimeoutTask(player, limboPlayer); + limboServiceHelper.revokeLimboStates(player); + entries.put(name, limboPlayer); + } + + /** + * Returns the limbo player for the given name, or null otherwise. + * + * @param name the name to retrieve the data for + * @return the associated limbo player, or null if none available + */ + public LimboPlayer getLimboPlayer(String name) { + return entries.get(name.toLowerCase()); + } + + /** + * Returns whether there is a limbo player for the given name. + * + * @param name the name to check + * @return true if present, false otherwise + */ + public boolean hasLimboPlayer(String name) { + return entries.containsKey(name.toLowerCase()); + } /** * Restores the limbo data and subsequently deletes the entry. @@ -65,51 +101,9 @@ public class LimboService { } } - /** - * Returns the limbo player for the given name, or null otherwise. - * - * @param name the name to retrieve the data for - * @return the associated limbo player, or null if none available - */ - public LimboPlayer getLimboPlayer(String name) { - return entries.get(name.toLowerCase()); - } - - /** - * Returns whether there is a limbo player for the given name. - * - * @param name the name to check - * @return true if present, false otherwise - */ - public boolean hasLimboPlayer(String name) { - return entries.containsKey(name.toLowerCase()); - } - - /** - * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player. - * - * @param player the player to process - * @param isRegistered whether or not the player is registered - */ - public void createLimboPlayer(Player player, boolean isRegistered) { - final String name = player.getName().toLowerCase(); - - LimboPlayer existingLimbo = entries.remove(name); - if (existingLimbo != null) { - existingLimbo.clearTasks(); - ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name); - } - - LimboPlayer limboPlayer = newLimboPlayer(player, isRegistered); - taskManager.registerMessageTask(player, limboPlayer, isRegistered); - taskManager.registerTimeoutTask(player, limboPlayer); - revokeLimboStates(player); - entries.put(name, limboPlayer); - } - /** * Creates new tasks for the given player and cancels the old ones for a newly registered player. - * This resets his time to log in (TimeoutTask) and updates the messages he is shown (MessageTask). + * This resets his time to log in (TimeoutTask) and updates the message he is shown (MessageTask). * * @param player the player to reset the tasks for */ @@ -148,48 +142,6 @@ public class LimboService { .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false)); } - /** - * Creates a LimboPlayer with the given player's details. - * - * @param player the player to process - * @param isRegistered whether the player is registered - * @return limbo player with the player's data - */ - private LimboPlayer newLimboPlayer(Player player, boolean isRegistered) { - Location location = spawnLoader.getPlayerLocationOrSpawn(player); - // For safety reasons an unregistered player should not have OP status after registration - boolean isOperator = isRegistered && player.isOp(); - boolean flyEnabled = player.getAllowFlight(); - float walkSpeed = player.getWalkSpeed(); - float flySpeed = player.getFlySpeed(); - String playerGroup = ""; - if (permissionsManager.hasGroupSupport()) { - playerGroup = permissionsManager.getPrimaryGroup(player); - } - ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); - - return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed); - } - - /** - * Removes the data that is saved in a LimboPlayer from the player. - *

    - * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and - * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. - * - * @param player the player to set defaults to - */ - private void revokeLimboStates(Player player) { - player.setOp(false); - player.setAllowFlight(false); - - if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { - player.setFlySpeed(0.0f); - player.setWalkSpeed(0.0f); - } - } - /** * Returns the limbo player for the given player or logs an error. * diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java new file mode 100644 index 000000000..fc6933461 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java @@ -0,0 +1,103 @@ +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.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Helper class for the LimboService. + */ +class LimboServiceHelper { + + @Inject + private SpawnLoader spawnLoader; + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private Settings settings; + + /** + * Creates a LimboPlayer with the given player's details. + * + * @param player the player to process + * @param isRegistered whether the player is registered + * @return limbo player with the player's data + */ + LimboPlayer createLimboPlayer(Player player, boolean isRegistered) { + Location location = spawnLoader.getPlayerLocationOrSpawn(player); + // For safety reasons an unregistered player should not have OP status after registration + boolean isOperator = isRegistered && player.isOp(); + boolean flyEnabled = player.getAllowFlight(); + float walkSpeed = player.getWalkSpeed(); + float flySpeed = player.getFlySpeed(); + String playerGroup = permissionsManager.hasGroupSupport() + ? permissionsManager.getPrimaryGroup(player) : ""; + ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup); + + return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed); + } + + /** + * Removes the data that is saved in a LimboPlayer from the player. + *

    + * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and + * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}. + * + * @param player the player to set defaults to + */ + void revokeLimboStates(Player player) { + player.setOp(false); + player.setAllowFlight(false); + + if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) + && settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { + player.setFlySpeed(0.0f); + player.setWalkSpeed(0.0f); + } + } + + /** + * Merges two existing LimboPlayer instances of a player. Merging is done the following way: + *

      + *
    • isOperator, allowFlight: true if either limbo has true
    • + *
    • flySpeed, walkSpeed: maximum value of either limbo player
    • + *
    • group: from old limbo if not empty, otherwise from new limbo
    • + *
    • location: from old limbo
    • + *
    + * + * @param newLimbo the new limbo player + * @param oldLimbo the old limbo player + * @return merged limbo player if both arguments are not null, otherwise the first non-null argument + */ + LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) { + if (newLimbo == null) { + return oldLimbo; + } else if (oldLimbo == null) { + return newLimbo; + } + + boolean isOperator = newLimbo.isOperator() || oldLimbo.isOperator(); + boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly(); + float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed()); + float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed()); + String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup()); + + return new LimboPlayer(oldLimbo.getLocation(), isOperator, group, canFly, walkSpeed, flySpeed); + } + + private static String firstNotEmpty(String newGroup, String oldGroup) { + ConsoleLogger.debug("Limbo merge: new and old perm groups are `{0}` and `{1}`", newGroup, oldGroup); + if ("".equals(oldGroup)) { + return newGroup; + } + return oldGroup; + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java new file mode 100644 index 000000000..77a21d692 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java @@ -0,0 +1,82 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.Location; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link LimboServiceHelper}. + *

    + * Note: some methods are tested directly where they are used via {@link LimboServiceTest}. + */ +@RunWith(MockitoJUnitRunner.class) +public class LimboServiceHelperTest { + + @InjectMocks + private LimboServiceHelper limboServiceHelper; + + @Test + public void shouldMergeLimboPlayers() { + // given + Location newLocation = mock(Location.class); + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", false, 0.0f, 0.0f); + Location oldLocation = mock(Location.class); + LimboPlayer oldLimbo = new LimboPlayer(oldLocation, true, "grp-old", true, 0.1f, 0.8f); + + // when + LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); + + // then + assertThat(result.getLocation(), equalTo(oldLocation)); + assertThat(result.isOperator(), equalTo(true)); + assertThat(result.getGroup(), equalTo("grp-old")); + assertThat(result.isCanFly(), equalTo(true)); + assertThat(result.getWalkSpeed(), equalTo(0.1f)); + assertThat(result.getFlySpeed(), equalTo(0.8f)); + } + + @Test + public void shouldFallBackToNewLimboForMissingData() { + // given + Location newLocation = mock(Location.class); + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", true, 0.3f, 0.0f); + Location oldLocation = mock(Location.class); + LimboPlayer oldLimbo = new LimboPlayer(oldLocation, false, "", false, 0.1f, 0.1f); + + // when + LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); + + // then + assertThat(result.getLocation(), equalTo(oldLocation)); + assertThat(result.isOperator(), equalTo(false)); + assertThat(result.getGroup(), equalTo("grp-new")); + assertThat(result.isCanFly(), equalTo(true)); + assertThat(result.getWalkSpeed(), equalTo(0.3f)); + assertThat(result.getFlySpeed(), equalTo(0.1f)); + } + + @Test + public void shouldHandleNullInputs() { + // given + LimboPlayer limbo = mock(LimboPlayer.class); + + // when + LimboPlayer result1 = limboServiceHelper.merge(limbo, null); + LimboPlayer result2 = limboServiceHelper.merge(null, limbo); + LimboPlayer result3 = limboServiceHelper.merge(null, null); + + // then + verifyZeroInteractions(limbo); + assertThat(result1, equalTo(limbo)); + assertThat(result2, equalTo(limbo)); + assertThat(result3, nullValue()); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java index 4abe481ce..a2e791fab 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -1,5 +1,7 @@ package fr.xephi.authme.data.limbo; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.permission.PermissionsManager; @@ -13,10 +15,8 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; import java.util.Map; @@ -34,14 +34,17 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Test for {@link LimboService}. + * Test for {@link LimboService}, and {@link LimboServiceHelper}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(DelayedInjectionRunner.class) public class LimboServiceTest { - @InjectMocks + @InjectDelayed private LimboService limboService; + @InjectDelayed + private LimboServiceHelper limboServiceHelper; + @Mock private SpawnLoader spawnLoader; From 8557621c02c13c9b172302f547ad0180d9fe64ac Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 12 Mar 2017 18:43:37 +0100 Subject: [PATCH 082/125] #1125 Create infrastructure for Limbo persistence + restore 5.2 JSON storage - Introduce configurable storage mechanism - LimboPersistence wraps a LimboPersistenceHandler, of which there are multiple implementations - Outside of the limbo.persistence package, classes only talk to LimboPersistence - Restore the way of persisting to JSON from 5.2 (SeparateFilePersistenceHandler) - Add handling for stored limbo players - Merge any existing LimboPlayers together with the goal of only keeping one version of a LimboPlayer: there is no way for a player to be online without triggering the creation of a LimboPlayer first, so we can guarantee that the in-memory LimboPlayer is the most up-to-date, i.e. when restoring limbo data we don't have to check against the disk. - Create and delete LimboPlayers at the same time when LimboPlayers are added or removed from the in-memory map - Catch all exceptions in LimboPersistence so a handler throwing an unexpected exception does not stop the limbo process (#1070) - Extend debug command /authme debug limbo to show LimboPlayer information on disk, too --- .checkstyle.xml | 1 + .../executable/authme/debug/DebugCommand.java | 4 +- .../authme/debug/LimboPlayerViewer.java | 42 +++-- .../xephi/authme/data/limbo/LimboService.java | 21 ++- .../authme/data/limbo/LimboServiceHelper.java | 10 +- .../limbo/persistence/LimboPersistence.java | 82 ++++++++ .../persistence/LimboPersistenceHandler.java | 39 ++++ .../persistence/LimboPersistenceType.java | 29 +++ .../persistence/NoOpPersistenceHandler.java | 30 +++ .../SeparateFilePersistenceHandler.java | 178 ++++++++++++++++++ .../settings/properties/LimboSettings.java | 12 +- .../data/limbo/LimboServiceHelperTest.java | 5 +- .../authme/data/limbo/LimboServiceTest.java | 4 + .../persistence/LimboPersistenceTest.java | 175 +++++++++++++++++ .../SeparateFilePersistenceHandlerTest.java | 127 +++++++++++++ .../AuthMeSettingsRetrieverTest.java | 2 +- 16 files changed, 733 insertions(+), 28 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java diff --git a/.checkstyle.xml b/.checkstyle.xml index 4ff5d8dfb..ca570f34f 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -159,6 +159,7 @@ + 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 ae3dc22f1..5166df0da 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,10 +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.TreeMap; /** * Debug command main. @@ -47,7 +47,7 @@ public class DebugCommand implements ExecutableCommand { // Lazy getter private Map getSections() { if (sections == null) { - Map sections = new HashMap<>(); + Map sections = new TreeMap<>(); for (Class sectionClass : sectionClasses) { DebugSection section = debugSectionFactory.newInstance(sectionClass); sections.put(section.getName(), section); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java index 5ac00fb18..c93ad2391 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -3,6 +3,7 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboService; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; import fr.xephi.authme.service.BukkitService; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -27,6 +28,9 @@ class LimboPlayerViewer implements DebugSection { @Inject private LimboService limboService; + @Inject + private LimboPersistence limboPersistence; + @Inject private BukkitService bukkitService; @@ -50,22 +54,23 @@ class LimboPlayerViewer implements DebugSection { return; } - LimboPlayer limbo = limboService.getLimboPlayer(arguments.get(0)); + LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0)); Player player = bukkitService.getPlayerExact(arguments.get(0)); - if (limbo == null && player == null) { + LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null; + if (memoryLimbo == null && player == null) { sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'"); return; } - sender.sendMessage(ChatColor.GOLD + "Showing limbo / player info for '" + arguments.get(0) + "'"); - new InfoDisplayer(sender, limbo, player) + sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'"); + new InfoDisplayer(sender, diskLimbo, memoryLimbo, player) .sendEntry("Is op", LimboPlayer::isOperator, Player::isOp) .sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed) .sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight) .sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed) .sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation())) .sendEntry("Group", LimboPlayer::getGroup, p -> ""); - sender.sendMessage("Note: group is only shown for LimboPlayer"); + sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups"); } /** @@ -102,25 +107,30 @@ class LimboPlayerViewer implements DebugSection { */ private static final class InfoDisplayer { private final CommandSender sender; - private final Optional limbo; + private final Optional diskLimbo; + private final Optional memoryLimbo; private final Optional player; /** * Constructor. * * @param sender command sender to send the information to - * @param limbo the limbo player to get data from + * @param memoryLimbo the limbo player to get data from * @param player the player to get data from */ - InfoDisplayer(CommandSender sender, LimboPlayer limbo, Player player) { + InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) { this.sender = sender; - this.limbo = Optional.ofNullable(limbo); + this.diskLimbo = Optional.ofNullable(diskLimbo); + this.memoryLimbo = Optional.ofNullable(memoryLimbo); this.player = Optional.ofNullable(player); - if (limbo == null) { + if (memoryLimbo == null) { sender.sendMessage("Note: no Limbo information available"); - } else if (player == null) { + } + if (player == null) { sender.sendMessage("Note: player is not online"); + } else if (diskLimbo == null) { + sender.sendMessage("Note: no Limbo on disk available"); } } @@ -138,10 +148,16 @@ class LimboPlayerViewer implements DebugSection { Function playerGetter) { sender.sendMessage( title + ": " - + limbo.map(limboGetter).map(String::valueOf).orElse("--") + + getData(diskLimbo, limboGetter) + " / " - + player.map(playerGetter).map(String::valueOf).orElse("--")); + + getData(memoryLimbo, limboGetter) + + " / " + + getData(player, playerGetter)); return this; } + + static String getData(Optional entity, Function getter) { + return entity.map(getter).map(String::valueOf).orElse(" -- "); + } } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index 290b79474..e78ca3139 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -1,6 +1,7 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -28,7 +29,10 @@ public class LimboService { private LimboPlayerTaskManager taskManager; @Inject - private LimboServiceHelper limboServiceHelper; + private LimboServiceHelper helper; + + @Inject + private LimboPersistence persistence; LimboService() { } @@ -42,19 +46,25 @@ public class LimboService { public void createLimboPlayer(Player player, boolean isRegistered) { final String name = player.getName().toLowerCase(); + LimboPlayer limboFromDisk = persistence.getLimboPlayer(player); + if (limboFromDisk != null) { + ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name); + } + LimboPlayer existingLimbo = entries.remove(name); if (existingLimbo != null) { existingLimbo.clearTasks(); - ConsoleLogger.debug("LimboPlayer for `{0}` was already present", name); + ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name); } - LimboPlayer limboPlayer = limboServiceHelper.merge( - limboServiceHelper.createLimboPlayer(player, isRegistered), existingLimbo); + LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk); + limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer); taskManager.registerMessageTask(player, limboPlayer, isRegistered); taskManager.registerTimeoutTask(player, limboPlayer); - limboServiceHelper.revokeLimboStates(player); + helper.revokeLimboStates(player); entries.put(name, limboPlayer); + persistence.saveLimboPlayer(player, limboPlayer); } /** @@ -98,6 +108,7 @@ public class LimboService { settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo); limbo.clearTasks(); ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName); + persistence.removeLimboPlayer(player); } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java index fc6933461..7373212f7 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java @@ -69,8 +69,7 @@ class LimboServiceHelper { *

      *
    • isOperator, allowFlight: true if either limbo has true
    • *
    • flySpeed, walkSpeed: maximum value of either limbo player
    • - *
    • group: from old limbo if not empty, otherwise from new limbo
    • - *
    • location: from old limbo
    • + *
    • group, location: from old limbo if not empty/null, otherwise from new limbo
    • *
    * * @param newLimbo the new limbo player @@ -89,8 +88,9 @@ class LimboServiceHelper { float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed()); float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed()); String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup()); + Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation()); - return new LimboPlayer(oldLimbo.getLocation(), isOperator, group, canFly, walkSpeed, flySpeed); + return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed); } private static String firstNotEmpty(String newGroup, String oldGroup) { @@ -100,4 +100,8 @@ class LimboServiceHelper { } return oldGroup; } + + private static Location firstNotNull(Location first, Location second) { + return first == null ? second : first; + } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java new file mode 100644 index 000000000..35cefb486 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java @@ -0,0 +1,82 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Handles the persistence of LimboPlayers. + */ +public class LimboPersistence implements SettingsDependent { + + private final Factory handlerFactory; + + private LimboPersistenceHandler handler; + + @Inject + LimboPersistence(Settings settings, Factory handlerFactory) { + this.handlerFactory = handlerFactory; + reload(settings); + } + + /** + * Retrieves the LimboPlayer for the given player if available. + * + * @param player the player to retrieve the LimboPlayer for + * @return the player's limbo player, or null if not available + */ + public LimboPlayer getLimboPlayer(Player player) { + try { + return handler.getLimboPlayer(player); + } catch (Exception e) { + ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e); + } + return null; + } + + /** + * Saves the given LimboPlayer for the provided player. + * + * @param player the player to save the LimboPlayer for + * @param limbo the limbo player to save + */ + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + try { + handler.saveLimboPlayer(player, limbo); + } catch (Exception e) { + ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e); + } + } + + /** + * Removes the LimboPlayer for the given player. + * + * @param player the player whose LimboPlayer should be removed + */ + public void removeLimboPlayer(Player player) { + try { + handler.removeLimboPlayer(player); + } catch (Exception e) { + ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e); + } + } + + @Override + public void reload(Settings settings) { + LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE); + if (handler == null || handler.getType() != persistenceType) { + // If we're changing from an existing handler, output a quick hint that nothing is converted. + if (handler != null) { + ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted."); + } + + handler = handlerFactory.newInstance(persistenceType.getImplementationClass()); + } + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java new file mode 100644 index 000000000..95e88aada --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java @@ -0,0 +1,39 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.entity.Player; + +/** + * Handles I/O for storing LimboPlayer objects. + */ +interface LimboPersistenceHandler { + + /** + * Returns the limbo player for the given player if it exists. + * + * @param player the player + * @return the stored limbo player, or null if not available + */ + LimboPlayer getLimboPlayer(Player player); + + /** + * Saves the given limbo player for the given player to the disk. + * + * @param player the player to save the limbo player for + * @param limbo the limbo player to save + */ + void saveLimboPlayer(Player player, LimboPlayer limbo); + + /** + * Removes the limbo player from the disk. + * + * @param player the player whose limbo player should be removed + */ + void removeLimboPlayer(Player player); + + /** + * @return the type of the limbo persistence implementation + */ + LimboPersistenceType getType(); + +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java new file mode 100644 index 000000000..9e6d8fe91 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java @@ -0,0 +1,29 @@ +package fr.xephi.authme.data.limbo.persistence; + +/** + * Types of persistence for LimboPlayer objects. + */ +public enum LimboPersistenceType { + + INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class), + + DISABLED(NoOpPersistenceHandler.class); + + private final Class implementationClass; + + /** + * Constructor. + * + * @param implementationClass the implementation class + */ + LimboPersistenceType(Class implementationClass) { + this.implementationClass= implementationClass; + } + + /** + * @return class implementing the persistence type + */ + public Class getImplementationClass() { + return implementationClass; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java new file mode 100644 index 000000000..ac6ff9b36 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.data.limbo.persistence; + +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.entity.Player; + +/** + * Limbo player persistence implementation that does nothing. + */ +class NoOpPersistenceHandler implements LimboPersistenceHandler { + + @Override + public LimboPlayer getLimboPlayer(Player player) { + return null; + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + // noop + } + + @Override + public void removeLimboPlayer(Player player) { + // noop + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.DISABLED; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java new file mode 100644 index 000000000..60927cf09 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java @@ -0,0 +1,178 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +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.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; + +/** + * Saves LimboPlayer objects as JSON into individual files. + */ +class SeparateFilePersistenceHandler implements LimboPersistenceHandler { + + private final Gson gson; + private final File cacheDir; + private final BukkitService bukkitService; + + @Inject + SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) { + this.bukkitService = bukkitService; + + cacheDir = new File(dataFolder, "playerdata"); + if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) { + ConsoleLogger.warning("Failed to create userdata directory."); + } + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer()) + .setPrettyPrinting() + .create(); + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + String id = PlayerUtils.getUUIDorName(player); + File file = new File(cacheDir, id + File.separator + "data.json"); + if (!file.exists()) { + return null; + } + + try { + String str = Files.toString(file, StandardCharsets.UTF_8); + return gson.fromJson(str, LimboPlayer.class); + } catch (IOException e) { + ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e); + return null; + } + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) { + String id = PlayerUtils.getUUIDorName(player); + try { + File file = new File(cacheDir, id + File.separator + "data.json"); + Files.createParentDirs(file); + Files.touch(file); + Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8); + } catch (IOException e) { + ConsoleLogger.logException("Failed to write " + player.getName() + " data:", e); + } + } + + /** + * Removes the LimboPlayer. This will delete the + * "playerdata/<uuid or name>/" folder from disk. + * + * @param player player to remove + */ + @Override + public void removeLimboPlayer(Player player) { + String id = PlayerUtils.getUUIDorName(player); + File file = new File(cacheDir, id); + if (file.exists()) { + FileUtils.purgeDirectory(file); + FileUtils.delete(file); + } + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.INDIVIDUAL_FILES; + } + + private final class LimboPlayerDeserializer implements JsonDeserializer { + + @Override + public LimboPlayer deserialize(JsonElement jsonElement, Type type, + JsonDeserializationContext context) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + if (jsonObject == null) { + return null; + } + + Location loc = null; + String group = ""; + boolean operator = false; + boolean canFly = false; + float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; + float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; + + JsonElement e; + if ((e = jsonObject.getAsJsonObject("location")) != null) { + JsonObject obj = e.getAsJsonObject(); + World world = bukkitService.getWorld(obj.get("world").getAsString()); + if (world != null) { + double x = obj.get("x").getAsDouble(); + double y = obj.get("y").getAsDouble(); + double z = obj.get("z").getAsDouble(); + float yaw = obj.get("yaw").getAsFloat(); + float pitch = obj.get("pitch").getAsFloat(); + loc = new Location(world, x, y, z, yaw, pitch); + } + } + if ((e = jsonObject.get("group")) != null) { + group = e.getAsString(); + } + if ((e = jsonObject.get("operator")) != null) { + operator = e.getAsBoolean(); + } + if ((e = jsonObject.get("can-fly")) != null) { + canFly = e.getAsBoolean(); + } + if ((e = jsonObject.get("walk-speed")) != null) { + walkSpeed = e.getAsFloat(); + } + if ((e = jsonObject.get("fly-speed")) != null) { + flySpeed = e.getAsFloat(); + } + + return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); + } + } + + private static final class LimboPlayerSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(LimboPlayer limboPlayer, Type type, + JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.addProperty("group", limboPlayer.getGroup()); + + Location loc = limboPlayer.getLocation(); + JsonObject obj2 = new JsonObject(); + obj2.addProperty("world", loc.getWorld().getName()); + obj2.addProperty("x", loc.getX()); + obj2.addProperty("y", loc.getY()); + obj2.addProperty("z", loc.getZ()); + obj2.addProperty("yaw", loc.getYaw()); + obj2.addProperty("pitch", loc.getPitch()); + obj.add("location", obj2); + + obj.addProperty("operator", limboPlayer.isOperator()); + obj.addProperty("can-fly", limboPlayer.isCanFly()); + obj.addProperty("walk-speed", limboPlayer.getWalkSpeed()); + obj.addProperty("fly-speed", limboPlayer.getFlySpeed()); + return obj; + } + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java index f861c4990..1e9d80d5c 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -7,6 +7,7 @@ import ch.jalu.configme.properties.Property; import com.google.common.collect.ImmutableMap; import fr.xephi.authme.data.limbo.AllowFlightRestoreType; import fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType; +import fr.xephi.authme.data.limbo.persistence.LimboPersistenceType; import java.util.Map; @@ -17,6 +18,15 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty; */ public final class LimboSettings implements SettingsHolder { + @Comment({ + "Besides storing the data in memory, you can define if/how the data should be persisted", + "on disk. This is useful in case of a server crash, so next time the server starts we can", + "properly restore things like OP status, ability to fly, and walk/fly speed.", + "DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file" + }) + public static final Property LIMBO_PERSISTENCE_TYPE = + newProperty(LimboPersistenceType.class, "limbo.persistence", LimboPersistenceType.INDIVIDUAL_FILES); + @Comment({ "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.", "RESTORE sets back the old property from the player." @@ -50,7 +60,7 @@ public final class LimboSettings implements SettingsHolder { "Before a user logs in, various properties are temporarily removed from the player,", "such as OP status, ability to fly, and walk/fly speed.", "Once the user is logged in, we add back the properties we previously saved.", - "In this section, you may define how the properties should be restored." + "In this section, you may define how these properties should be handled." }; return ImmutableMap.of("limbo", limboExplanation); } diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java index 77a21d692..43f110331 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java @@ -48,14 +48,13 @@ public class LimboServiceHelperTest { // given Location newLocation = mock(Location.class); LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", true, 0.3f, 0.0f); - Location oldLocation = mock(Location.class); - LimboPlayer oldLimbo = new LimboPlayer(oldLocation, false, "", false, 0.1f, 0.1f); + LimboPlayer oldLimbo = new LimboPlayer(null, false, "", false, 0.1f, 0.1f); // when LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); // then - assertThat(result.getLocation(), equalTo(oldLocation)); + assertThat(result.getLocation(), equalTo(newLocation)); assertThat(result.isOperator(), equalTo(false)); assertThat(result.getGroup(), equalTo("grp-new")); assertThat(result.isCanFly(), equalTo(true)); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java index a2e791fab..807024cd6 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -4,6 +4,7 @@ import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; @@ -57,6 +58,9 @@ public class LimboServiceTest { @Mock private LimboPlayerTaskManager taskManager; + @Mock + private LimboPersistence limboPersistence; + @BeforeClass public static void initLogger() { TestHelper.setupLogger(); diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java new file mode 100644 index 000000000..3511771dd --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java @@ -0,0 +1,175 @@ +package fr.xephi.authme.data.limbo.persistence; + +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.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.entity.Player; +import org.hamcrest.Matcher; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; + +import java.util.logging.Logger; + +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +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.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link LimboPersistence}. + */ +@RunWith(DelayedInjectionRunner.class) +public class LimboPersistenceTest { + + @InjectDelayed + private LimboPersistence limboPersistence; + + @Mock + private Factory handlerFactory; + + @Mock + private Settings settings; + + @BeforeClass + public static void setUpLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + @SuppressWarnings("unchecked") + public void setUpMocks() { + given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)).willReturn(LimboPersistenceType.DISABLED); + given(handlerFactory.newInstance(any(Class.class))) + .willAnswer(invocation -> mock(invocation.getArgument(0))); + } + + @Test + public void shouldInitializeProperly() { + // given / when / then + assertThat(getHandler(), instanceOf(NoOpPersistenceHandler.class)); + } + + @Test + public void shouldDelegateToHandler() { + // given + Player player = mock(Player.class); + LimboPersistenceHandler handler = getHandler(); + LimboPlayer limbo = mock(LimboPlayer.class); + given(handler.getLimboPlayer(player)).willReturn(limbo); + + // when + LimboPlayer result = limboPersistence.getLimboPlayer(player); + limboPersistence.saveLimboPlayer(player, mock(LimboPlayer.class)); + limboPersistence.removeLimboPlayer(mock(Player.class)); + + // then + assertThat(result, equalTo(limbo)); + verify(handler).getLimboPlayer(player); + verify(handler).saveLimboPlayer(eq(player), argThat(notNullAndDifferentFrom(limbo))); + verify(handler).removeLimboPlayer(argThat(notNullAndDifferentFrom(player))); + } + + @Test + public void shouldReloadProperly() { + // given + given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)) + .willReturn(LimboPersistenceType.INDIVIDUAL_FILES); + + // when + limboPersistence.reload(settings); + + // then + assertThat(getHandler(), instanceOf(LimboPersistenceType.INDIVIDUAL_FILES.getImplementationClass())); + } + + @Test + public void shouldNotReinitializeHandlerForSameType() { + // given + LimboPersistenceHandler currentHandler = getHandler(); + Mockito.reset(handlerFactory); + given(currentHandler.getType()).willCallRealMethod(); + + // when + limboPersistence.reload(settings); + + // then + verifyZeroInteractions(handlerFactory); + assertThat(currentHandler, sameInstance(getHandler())); + } + + @Test + public void shouldHandleExceptionWhenGettingLimbo() { + // given + Player player = mock(Player.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(IllegalAccessException.class).when(handler).getLimboPlayer(player); + + // when + LimboPlayer result = limboPersistence.getLimboPlayer(player); + + // then + assertThat(result, nullValue()); + verify(logger).warning(argThat(containsString("[IllegalAccessException]"))); + } + + @Test + public void shouldHandleExceptionWhenSavingLimbo() { + // given + Player player = mock(Player.class); + LimboPlayer limbo = mock(LimboPlayer.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(IllegalStateException.class).when(handler).saveLimboPlayer(player, limbo); + + // when + limboPersistence.saveLimboPlayer(player, limbo); + + // then + verify(logger).warning(argThat(containsString("[IllegalStateException]"))); + } + + @Test + public void shouldHandleExceptionWhenRemovingLimbo() { + // given + Player player = mock(Player.class); + Logger logger = TestHelper.setupLogger(); + LimboPersistenceHandler handler = getHandler(); + doThrow(UnsupportedOperationException.class).when(handler).removeLimboPlayer(player); + + // when + limboPersistence.removeLimboPlayer(player); + + // then + verify(logger).warning(argThat(containsString("[UnsupportedOperationException]"))); + } + + private LimboPersistenceHandler getHandler() { + return ReflectionTestUtils.getFieldValue(LimboPersistence.class, limboPersistence, "handler"); + } + + private static Matcher notNullAndDifferentFrom(T o) { + return both(not(sameInstance(o))).and(not(nullValue())); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java new file mode 100644 index 000000000..a4e7dc93f --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandlerTest.java @@ -0,0 +1,127 @@ +package fr.xephi.authme.data.limbo.persistence; + +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.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import org.bukkit.Location; +import org.bukkit.World; +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.UUID; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link SeparateFilePersistenceHandler}. + */ +@RunWith(DelayedInjectionRunner.class) +public class SeparateFilePersistenceHandlerTest { + + private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PersistenceTest".getBytes()); + private static final String SOURCE_FOLDER = TestHelper.PROJECT_ROOT + "data/backup/"; + + @InjectDelayed + private SeparateFilePersistenceHandler handler; + + @Mock + private BukkitService bukkitService; + + @DataFolder + private File dataFolder; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeInjecting + public void copyTestFiles() throws IOException { + dataFolder = temporaryFolder.newFolder(); + File playerFolder = new File(dataFolder, FileUtils.makePath("playerdata", SAMPLE_UUID.toString())); + if (!playerFolder.mkdirs()) { + throw new IllegalStateException("Cannot create '" + playerFolder.getAbsolutePath() + "'"); + } + Files.copy(TestHelper.getJarPath(FileUtils.makePath(SOURCE_FOLDER, "sample-folder", "data.json")), + new File(playerFolder, "data.json").toPath()); + } + + @Test + public void shouldReadDataFromFile() { + // given + Player player = mock(Player.class); + given(player.getUniqueId()).willReturn(SAMPLE_UUID); + World world = mock(World.class); + given(bukkitService.getWorld("nether")).willReturn(world); + + // when + LimboPlayer data = handler.getLimboPlayer(player); + + // then + assertThat(data, not(nullValue())); + assertThat(data.isOperator(), equalTo(true)); + assertThat(data.isCanFly(), equalTo(true)); + assertThat(data.getWalkSpeed(), equalTo(0.2f)); + assertThat(data.getFlySpeed(), equalTo(0.1f)); + assertThat(data.getGroup(), equalTo("players")); + Location location = data.getLocation(); + assertThat(location.getX(), equalTo(-113.219)); + assertThat(location.getY(), equalTo(72.0)); + assertThat(location.getZ(), equalTo(130.637)); + assertThat(location.getWorld(), equalTo(world)); + assertThat(location.getPitch(), equalTo(24.15f)); + assertThat(location.getYaw(), equalTo(-292.484f)); + } + + @Test + public void shouldReturnNullForUnavailablePlayer() { + // given + Player player = mock(Player.class); + given(player.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("other-player".getBytes())); + + // when + LimboPlayer data = handler.getLimboPlayer(player); + + // then + assertThat(data, nullValue()); + } + + @Test + public void shouldSavePlayerData() { + // given + Player player = mock(Player.class); + UUID uuid = UUID.nameUUIDFromBytes("New player".getBytes()); + given(player.getUniqueId()).willReturn(uuid); + + + World world = mock(World.class); + given(world.getName()).willReturn("player-world"); + Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f); + String group = "primary-grp"; + LimboPlayer limbo = new LimboPlayer(location, true, group, true, 1.2f, 0.8f); + + // when + handler.saveLimboPlayer(player, limbo); + + // then + File playerFile = new File(dataFolder, FileUtils.makePath("playerdata", uuid.toString(), "data.json")); + assertThat(playerFile.exists(), equalTo(true)); + // TODO ljacqu 20160711: Check contents of file + } + +} diff --git a/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java b/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java index 2a1c588e6..fcb1228c9 100644 --- a/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java +++ b/src/test/java/fr/xephi/authme/settings/properties/AuthMeSettingsRetrieverTest.java @@ -22,7 +22,7 @@ public class AuthMeSettingsRetrieverTest { // an error margin of 10: this prevents us from having to adjust the test every time the config is changed. // If this test fails, replace the first argument in closeTo() with the new number of properties assertThat((double) configurationData.getProperties().size(), - closeTo(150, 10)); + closeTo(160, 10)); } @Test From fd18930286ca65284dfd0d15f08b3f018871542b Mon Sep 17 00:00:00 2001 From: rafael59r2 Date: Sun, 12 Mar 2017 17:44:29 +0000 Subject: [PATCH 083/125] 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 fef35f74f..56ad17329 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 084/125] 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 56ad17329..c926a9129 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 710198833cab54516d940326cd3216cfebe81b90 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 13 Mar 2017 18:30:52 +0100 Subject: [PATCH 085/125] #1125 Create SingleFilePersistenceHandler + extract (de)serializer for LimboPlayer --- .../persistence/LimboPersistenceType.java | 2 + .../persistence/LimboPlayerDeserializer.java | 115 ++++++++++++++++++ .../persistence/LimboPlayerSerializer.java | 52 ++++++++ .../SeparateFilePersistenceHandler.java | 92 +------------- .../SingleFilePersistenceHandler.java | 94 ++++++++++++++ .../settings/properties/LimboSettings.java | 5 +- 6 files changed, 268 insertions(+), 92 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java index 9e6d8fe91..294ccbff5 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java @@ -7,6 +7,8 @@ public enum LimboPersistenceType { INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class), + SINGLE_FILE(SingleFilePersistenceHandler.class), + DISABLED(NoOpPersistenceHandler.class); private final Class implementationClass; diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java new file mode 100644 index 000000000..94e1950b1 --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java @@ -0,0 +1,115 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.Location; +import org.bukkit.World; + +import java.lang.reflect.Type; +import java.util.function.Function; + +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUP; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_PITCH; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_WORLD; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_X; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Y; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_YAW; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Z; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.WALK_SPEED; + +/** + * Converts a JsonElement to a LimboPlayer. + */ +class LimboPlayerDeserializer implements JsonDeserializer { + + private BukkitService bukkitService; + + LimboPlayerDeserializer(BukkitService bukkitService) { + this.bukkitService = bukkitService; + } + + @Override + public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + if (jsonObject == null) { + return null; + } + + Location loc = deserializeLocation(jsonObject); + boolean operator = getBoolean(jsonObject, IS_OP); + String group = getString(jsonObject, GROUP); + boolean canFly = getBoolean(jsonObject, CAN_FLY); + float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED); + float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED); + + return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); + } + + private Location deserializeLocation(JsonObject jsonObject) { + JsonElement e; + if ((e = jsonObject.getAsJsonObject(LOCATION)) != null) { + JsonObject locationObject = e.getAsJsonObject(); + World world = bukkitService.getWorld(getString(locationObject, LOC_WORLD)); + if (world != null) { + double x = getDouble(locationObject, LOC_X); + double y = getDouble(locationObject, LOC_Y); + double z = getDouble(locationObject, LOC_Z); + float yaw = getFloat(locationObject, LOC_YAW); + float pitch = getFloat(locationObject, LOC_PITCH); + return new Location(world, x, y, z, yaw, pitch); + } + } + return null; + } + + private static String getString(JsonObject jsonObject, String memberName) { + JsonElement element = jsonObject.get(memberName); + return element != null ? element.getAsString() : ""; + } + + private static boolean getBoolean(JsonObject jsonObject, String memberName) { + JsonElement element = jsonObject.get(memberName); + return element != null && element.getAsBoolean(); + } + + private static float getFloat(JsonObject jsonObject, String memberName) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, 0.0f); + } + + private static float getFloat(JsonObject jsonObject, String memberName, float defaultValue) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, defaultValue); + } + + private static double getDouble(JsonObject jsonObject, String memberName) { + return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsDouble, 0.0); + } + + /** + * Gets a number from the given JsonElement safely. + * + * @param jsonElement the element to retrieve the number from + * @param numberFunction the function to get the number from the element + * @param defaultValue the value to return if the element is null or the number cannot be retrieved + * @param the number type + * @return the number from the given JSON element, or the default value + */ + private static N getNumberFromElement(JsonElement jsonElement, + Function numberFunction, + N defaultValue) { + if (jsonElement != null) { + try { + return numberFunction.apply(jsonElement); + } catch (NumberFormatException ignore) { + } + } + return defaultValue; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java new file mode 100644 index 000000000..aeae3b65b --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java @@ -0,0 +1,52 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import fr.xephi.authme.data.limbo.LimboPlayer; +import org.bukkit.Location; + +import java.lang.reflect.Type; + +/** + * Converts a LimboPlayer to a JsonElement. + */ +class LimboPlayerSerializer implements JsonSerializer { + + static final String LOCATION = "location"; + static final String LOC_WORLD = "world"; + static final String LOC_X = "x"; + static final String LOC_Y = "y"; + static final String LOC_Z = "z"; + static final String LOC_YAW = "yaw"; + static final String LOC_PITCH = "pitch"; + + static final String GROUP = "group"; + static final String IS_OP = "operator"; + static final String CAN_FLY = "can-fly"; + static final String WALK_SPEED = "walk-speed"; + static final String FLY_SPEED = "fly-speed"; + + + @Override + public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializationContext context) { + Location loc = limboPlayer.getLocation(); + JsonObject locationObject = new JsonObject(); + locationObject.addProperty(LOC_WORLD, loc.getWorld().getName()); + locationObject.addProperty(LOC_X, loc.getX()); + locationObject.addProperty(LOC_Y, loc.getY()); + locationObject.addProperty(LOC_Z, loc.getZ()); + locationObject.addProperty(LOC_YAW, loc.getYaw()); + locationObject.addProperty(LOC_PITCH, loc.getPitch()); + + JsonObject obj = new JsonObject(); + obj.add(LOCATION, locationObject); + obj.addProperty(GROUP, limboPlayer.getGroup()); + obj.addProperty(IS_OP, limboPlayer.isOperator()); + obj.addProperty(CAN_FLY, limboPlayer.isCanFly()); + obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed()); + obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed()); + return obj; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java index 60927cf09..438bce69d 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java @@ -3,26 +3,17 @@ package fr.xephi.authme.data.limbo.persistence; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -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.service.BukkitService; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.PlayerUtils; -import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Player; import javax.inject.Inject; import java.io.File; import java.io.IOException; -import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; /** @@ -32,19 +23,16 @@ class SeparateFilePersistenceHandler implements LimboPersistenceHandler { private final Gson gson; private final File cacheDir; - private final BukkitService bukkitService; @Inject SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) { - this.bukkitService = bukkitService; - cacheDir = new File(dataFolder, "playerdata"); if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) { - ConsoleLogger.warning("Failed to create userdata directory."); + ConsoleLogger.warning("Failed to create playerdata directory '" + cacheDir + "'"); } gson = new GsonBuilder() .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) - .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) .setPrettyPrinting() .create(); } @@ -99,80 +87,4 @@ class SeparateFilePersistenceHandler implements LimboPersistenceHandler { public LimboPersistenceType getType() { return LimboPersistenceType.INDIVIDUAL_FILES; } - - private final class LimboPlayerDeserializer implements JsonDeserializer { - - @Override - public LimboPlayer deserialize(JsonElement jsonElement, Type type, - JsonDeserializationContext context) { - JsonObject jsonObject = jsonElement.getAsJsonObject(); - if (jsonObject == null) { - return null; - } - - Location loc = null; - String group = ""; - boolean operator = false; - boolean canFly = false; - float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED; - float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED; - - JsonElement e; - if ((e = jsonObject.getAsJsonObject("location")) != null) { - JsonObject obj = e.getAsJsonObject(); - World world = bukkitService.getWorld(obj.get("world").getAsString()); - if (world != null) { - double x = obj.get("x").getAsDouble(); - double y = obj.get("y").getAsDouble(); - double z = obj.get("z").getAsDouble(); - float yaw = obj.get("yaw").getAsFloat(); - float pitch = obj.get("pitch").getAsFloat(); - loc = new Location(world, x, y, z, yaw, pitch); - } - } - if ((e = jsonObject.get("group")) != null) { - group = e.getAsString(); - } - if ((e = jsonObject.get("operator")) != null) { - operator = e.getAsBoolean(); - } - if ((e = jsonObject.get("can-fly")) != null) { - canFly = e.getAsBoolean(); - } - if ((e = jsonObject.get("walk-speed")) != null) { - walkSpeed = e.getAsFloat(); - } - if ((e = jsonObject.get("fly-speed")) != null) { - flySpeed = e.getAsFloat(); - } - - return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed); - } - } - - private static final class LimboPlayerSerializer implements JsonSerializer { - - @Override - public JsonElement serialize(LimboPlayer limboPlayer, Type type, - JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - obj.addProperty("group", limboPlayer.getGroup()); - - Location loc = limboPlayer.getLocation(); - JsonObject obj2 = new JsonObject(); - obj2.addProperty("world", loc.getWorld().getName()); - obj2.addProperty("x", loc.getX()); - obj2.addProperty("y", loc.getY()); - obj2.addProperty("z", loc.getZ()); - obj2.addProperty("yaw", loc.getYaw()); - obj2.addProperty("pitch", loc.getPitch()); - obj.add("location", obj2); - - obj.addProperty("operator", limboPlayer.isOperator()); - obj.addProperty("can-fly", limboPlayer.isCanFly()); - obj.addProperty("walk-speed", limboPlayer.getWalkSpeed()); - obj.addProperty("fly-speed", limboPlayer.getFlySpeed()); - return obj; - } - } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java new file mode 100644 index 000000000..57aa2dcff --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java @@ -0,0 +1,94 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Saves all LimboPlayers in one JSON file and keeps the entries in memory. + */ +class SingleFilePersistenceHandler implements LimboPersistenceHandler { + + private final File cacheFile; + private final Gson gson; + private Map entries; + + @Inject + SingleFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) { + cacheFile = new File(dataFolder, "limbo.json"); + if (!cacheFile.exists()) { + FileUtils.create(cacheFile); + } + + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) + .setPrettyPrinting() + .create(); + + Type type = new TypeToken>(){}.getType(); + try (FileReader fr = new FileReader(cacheFile)) { + entries = gson.fromJson(fr, type); + } catch (IOException e) { + ConsoleLogger.logException("Failed to read from '" + cacheFile + "':", e); + } + + if (entries == null) { + entries = new ConcurrentHashMap<>(); + } + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + return entries.get(PlayerUtils.getUUIDorName(player)); + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + entries.put(PlayerUtils.getUUIDorName(player), limbo); + saveEntries("adding '" + player.getName() + "'"); + } + + @Override + public void removeLimboPlayer(Player player) { + LimboPlayer entry = entries.remove(PlayerUtils.getUUIDorName(player)); + if (entry != null) { + saveEntries("removing '" + player.getName() + "'"); + } + } + + /** + * Saves the entries to the disk. + * + * @param action the reason for saving (for logging purposes) + */ + private void saveEntries(String action) { + try (FileWriter fw = new FileWriter(cacheFile)) { + gson.toJson(entries, fw); + } catch (IOException e) { + ConsoleLogger.logException("Failed saving JSON limbo cache after " + action, e); + } + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.SINGLE_FILE; + } +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java index 1e9d80d5c..1d7a29d75 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -22,10 +22,11 @@ public final class LimboSettings implements SettingsHolder { "Besides storing the data in memory, you can define if/how the data should be persisted", "on disk. This is useful in case of a server crash, so next time the server starts we can", "properly restore things like OP status, ability to fly, and walk/fly speed.", - "DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file" + "DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file,", + "SINGLE_FILE: all data in one single file (only if you have a small server!)" }) public static final Property LIMBO_PERSISTENCE_TYPE = - newProperty(LimboPersistenceType.class, "limbo.persistence", LimboPersistenceType.INDIVIDUAL_FILES); + newProperty(LimboPersistenceType.class, "limbo.persistence.type", LimboPersistenceType.INDIVIDUAL_FILES); @Comment({ "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.", From 9c3baa7f14c7756e03d38491d3e58414b02c247a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 13 Mar 2017 20:29:08 +0100 Subject: [PATCH 086/125] #1125 Create persistence of LimboPlayers in segment files (work in progress) - Instead of one huge file or a file for each player, allow the user to define how many files he wants to distribute the LimboPlayers over. This is based on a function that creates a String (segment ID) based on the player's UUID. --- .../persistence/LimboPersistenceType.java | 2 + .../persistence/SegmentConfiguration.java | 94 +++++++++++ .../SegmentFilesPersistenceHolder.java | 132 +++++++++++++++ .../limbo/persistence/SegmentNameBuilder.java | 60 +++++++ .../settings/properties/LimboSettings.java | 20 ++- .../persistence/SegmentConfigurationTest.java | 47 ++++++ .../persistence/SegmentNameBuilderTest.java | 153 ++++++++++++++++++ 7 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java create mode 100644 src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java index 294ccbff5..c998f95a4 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java @@ -9,6 +9,8 @@ public enum LimboPersistenceType { SINGLE_FILE(SingleFilePersistenceHandler.class), + SEGMENT_FILES(SegmentFilesPersistenceHolder.class), + DISABLED(NoOpPersistenceHandler.class); private final Class implementationClass; diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java new file mode 100644 index 000000000..d2f0d202a --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java @@ -0,0 +1,94 @@ +package fr.xephi.authme.data.limbo.persistence; + +/** + * Configuration for the total number of segments to use. + *

    + * The {@link SegmentFilesPersistenceHolder} reduces the number of files by assigning each UUID + * to a segment. This enum allows to define how many segments the UUIDs should be distributed in. + *

    + * Segments are defined by a distribution and a length. The distribution defines + * to how many outputs a single hexadecimal characters should be mapped. So e.g. a distribution + * of 3 means that all hexadecimal characters 0-f should be distributed over three different + * outputs evenly. The {@link SegmentNameBuilder} simply uses hexadecimal characters as outputs, + * so e.g. with a distribution of 3 all hex characters 0-f are mapped to 0, 1, or 2. + *

    + * To ensure an even distribution the segments must be powers of 2. Trivially, to implement a + * distribution of 16, the same character may be returned as was input (since 0-f make up 16 + * characters). A distribution of 1, on the other hand, means that the same output is returned + * regardless of the input character. + *

    + * The length parameter defines how many characters of a player's UUID should be used to + * create the segment ID. In other words, with a distribution of 2 and a length of 3, the first + * three characters of the UUID are taken into consideration, each mapped to one of two possible + * characters. For instance, a UUID starting with "0f5c9321" may yield the segment ID "010." + * Such a segment ID defines in which file the given UUID can be found and stored. + *

    + * The number of segments such a configuration yields is computed as {@code distribution ^ length}, + * since distribution defines how many outputs there are per digit, and length defines the number + * of digits. For instance, a distribution of 2 and a length of 3 will yield segment IDs 000, 001, + * 010, 011, 100, 101, 110 and 111 (i.e. all binary numbers from 0 to 7). + *

    + * There are multiple possibilities to achieve certain segment totals, e.g. 8 different segments + * may be created by setting distribution to 8 and length to 1, or distr. to 2 and length to 3. + * Where possible, prefer a length of 1 (no string concatenation required) or a distribution of + * 16 (no remapping of the characters required). + */ +public enum SegmentConfiguration { + + /** 1. */ + ONE(1, 1), + + /** 2. */ + TWO(2, 1), + + /** 4. */ + FOUR(4, 1), + + /** 8. */ + EIGHT(8, 1), + + /** 16. */ + SIXTEEN(16, 1), + + /** 32. */ + THIRTY_TWO(2, 5), + + /** 64. */ + SIXTY_FOUR(4, 3), + + /** 128. */ + ONE_TWENTY(2, 7), + + /** 256. */ + TWO_FIFTY(16, 2); + + private final int distribution; + private final int length; + + SegmentConfiguration(int distribution, int length) { + this.distribution = distribution; + this.length = length; + } + + /** + * @return the distribution size per character, i.e. how many possible outputs there are + * for any hexadecimal character + */ + public int getDistribution() { + return distribution; + } + + /** + * @return number of characters from a UUID that should be used to create a segment ID + */ + public int getLength() { + return length; + } + + /** + * @return number of segments to which this configuration will distribute UUIDs + */ + public int getTotalSegments() { + return (int) Math.pow(distribution, length); + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java new file mode 100644 index 000000000..24751fe0c --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java @@ -0,0 +1,132 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.io.Files; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import fr.xephi.authme.util.FileUtils; +import fr.xephi.authme.util.PlayerUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Persistence handler for LimboPlayer objects by distributing the objects to store + * in various segments (buckets) based on the start of the player's UUID. + */ +class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { + + private static final Type LIMBO_MAP_TYPE = new TypeToken>(){}.getType(); + + private final File cacheFolder; + private final Gson gson; + private final SegmentNameBuilder segmentNameBuilder; + + @Inject + SegmentFilesPersistenceHolder(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) { + cacheFolder = new File(dataFolder, "playerdata"); + if (!cacheFolder.exists()) { + // TODO ljacqu 20170313: Create FileUtils#mkdirs + cacheFolder.mkdirs(); + } + + gson = new GsonBuilder() + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) + .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService)) + .setPrettyPrinting() + .create(); + + segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION)); + + // TODO #1125: Check for other segment files and attempt to convert? + } + + @Override + public LimboPlayer getLimboPlayer(Player player) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + Map entries = readLimboPlayers(file); + return entries == null ? null : entries.get(uuid); + } + + @Override + public void saveLimboPlayer(Player player, LimboPlayer limbo) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + + Map entries = null; + if (file.exists()) { + entries = readLimboPlayers(file); + } else { + FileUtils.create(file); + } + /* intentionally separate if */ + if (entries == null) { + entries = new HashMap<>(); + } + + entries.put(PlayerUtils.getUUIDorName(player), limbo); + saveEntries(entries, file); + } + + @Override + public void removeLimboPlayer(Player player) { + String uuid = PlayerUtils.getUUIDorName(player); + File file = getPlayerSegmentFile(uuid); + if (file.exists()) { + Map entries = readLimboPlayers(file); + if (entries != null && entries.remove(PlayerUtils.getUUIDorName(player)) != null) { + saveEntries(entries, file); + } + } + } + + private void saveEntries(Map entries, File file) { + if (entries.isEmpty()) { + // TODO #1125: Probably should do a sweep of empty files on startup / shutdown, but not all the time + FileUtils.delete(file); + } else { + try (FileWriter fw = new FileWriter(file)) { + gson.toJson(entries, fw); + } catch (IOException e) { + ConsoleLogger.logException("Could not write to '" + file + "':", e); + } + } + } + + private Map readLimboPlayers(File file) { + if (!file.exists()) { + return null; + } + + try { + return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE); + } catch (IOException e) { + ConsoleLogger.logException("Failed reading '" + file + "':", e); + } + return null; + } + + private File getPlayerSegmentFile(String uuid) { + String segment = segmentNameBuilder.createSegmentName(uuid); + return new File(cacheFolder, segment + "-limbo.json"); + } + + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.SINGLE_FILE; + } +} diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java new file mode 100644 index 000000000..df517fbad --- /dev/null +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java @@ -0,0 +1,60 @@ +package fr.xephi.authme.data.limbo.persistence; + +import java.util.HashMap; +import java.util.Map; + +/** + * Creates segment names for {@link SegmentFilesPersistenceHolder}. + */ +class SegmentNameBuilder { + + private final int length; + private final int distribution; + private final String prefix; + private final Map charToSegmentChar; + + /** + * Constructor. + * + * @param partition the segment configuration + */ + SegmentNameBuilder(SegmentConfiguration partition) { + this.length = partition.getLength(); + this.distribution = partition.getDistribution(); + this.prefix = "seg" + partition.getTotalSegments() + "-"; + this.charToSegmentChar = buildCharMap(distribution); + } + + String createSegmentName(String uuid) { + if (distribution == 16) { + return prefix + uuid.substring(0, length); + } else { + return prefix + createSegmentName(uuid.substring(0, length).toCharArray()); + } + } + + private String createSegmentName(char[] chars) { + if (chars.length == 1) { + return String.valueOf(charToSegmentChar.get(chars[0])); + } + + StringBuilder sb = new StringBuilder(chars.length); + for (char chr : chars) { + sb.append(charToSegmentChar.get(chr)); + } + return sb.toString(); + } + + private static Map buildCharMap(int distribution) { + final char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + final int divisor = 16 / distribution; + + Map charToSegmentChar = new HashMap<>(); + for (int i = 0; i < hexChars.length; ++i) { + int mappedChar = i / divisor; + charToSegmentChar.put(hexChars[i], hexChars[mappedChar]); + } + return charToSegmentChar; + } + +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java index 1d7a29d75..d8d7104e7 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap; import fr.xephi.authme.data.limbo.AllowFlightRestoreType; import fr.xephi.authme.data.limbo.WalkFlySpeedRestoreType; import fr.xephi.authme.data.limbo.persistence.LimboPersistenceType; +import fr.xephi.authme.data.limbo.persistence.SegmentConfiguration; import java.util.Map; @@ -23,11 +24,28 @@ public final class LimboSettings implements SettingsHolder { "on disk. This is useful in case of a server crash, so next time the server starts we can", "properly restore things like OP status, ability to fly, and walk/fly speed.", "DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file,", - "SINGLE_FILE: all data in one single file (only if you have a small server!)" + "SINGLE_FILE: all data in one single file (only if you have a small server!)", + "SEGMENT_FILES: distributes players into different buckets based on their UUID. See below." }) public static final Property LIMBO_PERSISTENCE_TYPE = newProperty(LimboPersistenceType.class, "limbo.persistence.type", LimboPersistenceType.INDIVIDUAL_FILES); + @Comment({ + "This setting only affects SEGMENT_FILES persistence. The segment file", + "persistence attempts to reduce the number of files by distributing players into various", + "buckets based on their UUID. This setting defines into how many files the players should", + "be distributed. Possible values: ONE, TWO, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,", + "ONE_TWENTY for 128, TWO_FIFTY for 256.", + "For example, if you expect 100 non-logged in players, setting to SIXTEEN will average", + "6.25 players per file (100 / 16). If you set to ONE, like persistence SINGLE_FILE, only", + "one file will be used. Contrary to SINGLE_FILE, it won't keep the entries in cache, which", + "may deliver different results in terms of performance.", + "Note: if you change this setting you lose all stored LimboPlayer data because the", + "distribution of players will be different." + }) + public static final Property SEGMENT_DISTRIBUTION = + newProperty(SegmentConfiguration.class, "limbo.persistence.segmentDistribution", SegmentConfiguration.SIXTEEN); + @Comment({ "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.", "RESTORE sets back the old property from the player." diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java new file mode 100644 index 000000000..f5e5e1241 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java @@ -0,0 +1,47 @@ +package fr.xephi.authme.data.limbo.persistence; + +import com.google.common.collect.ImmutableSet; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.fail; + +/** + * Test for {@link SegmentConfiguration}. + */ +public class SegmentConfigurationTest { + + @Test + public void shouldHaveDistributionThatIsPowerOf2() { + // given + Set allowedDistributions = ImmutableSet.of(1, 2, 4, 8, 16); + + // when / then + for (SegmentConfiguration entry : SegmentConfiguration.values()) { + if (!allowedDistributions.contains(entry.getDistribution())) { + fail("Distribution must be a power of 2 and within [1, 16]. Offending item: " + entry); + } + } + } + + @Test + public void shouldHaveDifferentSegmentSizes() { + // given + Set sizes = new HashSet<>(); + + // when / then + for (SegmentConfiguration entry : SegmentConfiguration.values()) { + int segSize = (int) Math.pow(entry.getDistribution(), entry.getLength()); + assertThat(entry + " must have a positive segment size", + segSize, greaterThan(0)); + + assertThat(entry + " has a segment size that was already encountered (" + segSize + ")", + sizes.add(segSize), equalTo(true)); + } + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java new file mode 100644 index 000000000..41c3b0ffe --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java @@ -0,0 +1,153 @@ +package fr.xephi.authme.data.limbo.persistence; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.EIGHT; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.FOUR; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.ONE; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTEEN; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTY_FOUR; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.THIRTY_TWO; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.TWO; +import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.TWO_FIFTY; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; + +/** + * Test for {@link SegmentNameBuilder}. + */ +public class SegmentNameBuilderTest { + + /** + * Checks that using a given segment size really produces as many segments as defined. + * E.g. if we partition with {@link SegmentConfiguration#EIGHT} we expect eight different buckets. + */ + @Test + public void shouldCreatePromisedSizeOfSegments() { + for (SegmentConfiguration part : SegmentConfiguration.values()) { + // Perform this check only for `length` <= 5 because the test creates all hex numbers with `length` digits. + if (part.getLength() <= 5) { + checkTotalSegmentsProduced(part); + } + } + } + + private void checkTotalSegmentsProduced(SegmentConfiguration part) { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(part); + Set encounteredSegments = new HashSet<>(); + int shift = part.getLength() * 4; + // e.g. (1 << 16) - 1 = 0xFFFF. (Number of digits = shift/4, since 16 = 2^4) + int max = (1 << shift) - 1; + + // when + for (int i = 0; i <= max; ++i) { + String uuid = toPaddedHex(i, part.getLength()); + encounteredSegments.add(nameBuilder.createSegmentName(uuid)); + } + + // then + assertThat(encounteredSegments, hasSize(part.getTotalSegments())); + } + + private static String toPaddedHex(int dec, int padLength) { + String hexResult = Integer.toString(dec, 16); + while (hexResult.length() < padLength) { + hexResult = "0" + hexResult; + } + return hexResult; + } + + @Test + public void shouldCreateOneSegment() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(ONE); + + // when / then + assertThat(nameBuilder.createSegmentName("abc"), equalTo("seg1-0")); + assertThat(nameBuilder.createSegmentName("f0e"), equalTo("seg1-0")); + assertThat(nameBuilder.createSegmentName("329"), equalTo("seg1-0")); + } + + @Test + public void shouldCreateTwoSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(TWO); + + // when / then + assertThat(nameBuilder.createSegmentName("f6c"), equalTo("seg2-1")); + assertThat(nameBuilder.createSegmentName("29f"), equalTo("seg2-0")); + assertThat(nameBuilder.createSegmentName("983"), equalTo("seg2-1")); + } + + @Test + public void shouldCreateFourSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(FOUR); + + // when / then + assertThat(nameBuilder.createSegmentName("f9cc"), equalTo("seg4-3")); + assertThat(nameBuilder.createSegmentName("84c9"), equalTo("seg4-2")); + assertThat(nameBuilder.createSegmentName("3799"), equalTo("seg4-0")); + } + + @Test + public void shouldCreateEightSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(EIGHT); + + // when / then + assertThat(nameBuilder.createSegmentName("fc9c"), equalTo("seg8-7")); + assertThat(nameBuilder.createSegmentName("90ad"), equalTo("seg8-4")); + assertThat(nameBuilder.createSegmentName("35e4"), equalTo("seg8-1")); + assertThat(nameBuilder.createSegmentName("a39f"), equalTo("seg8-5")); + } + + @Test + public void shouldCreateSixteenSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(SIXTEEN); + + // when / then + assertThat(nameBuilder.createSegmentName("fc9a054"), equalTo("seg16-f")); + assertThat(nameBuilder.createSegmentName("b0a945e"), equalTo("seg16-b")); + assertThat(nameBuilder.createSegmentName("7afebab"), equalTo("seg16-7")); + } + + @Test + public void shouldCreateThirtyTwoSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(THIRTY_TWO); + + // when / then + assertThat(nameBuilder.createSegmentName("f890c9"), equalTo("seg32-11101")); + assertThat(nameBuilder.createSegmentName("49c39a"), equalTo("seg32-01101")); + assertThat(nameBuilder.createSegmentName("b75d09"), equalTo("seg32-10010")); + } + + @Test + public void shouldCreateSixtyFourSegments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(SIXTY_FOUR); + + // when / then + assertThat(nameBuilder.createSegmentName("82f"), equalTo("seg64-203")); + assertThat(nameBuilder.createSegmentName("9b4"), equalTo("seg64-221")); + assertThat(nameBuilder.createSegmentName("068"), equalTo("seg64-012")); + } + + @Test + public void shouldCreate256Segments() { + // given + SegmentNameBuilder nameBuilder = new SegmentNameBuilder(TWO_FIFTY); + + // when / then + assertThat(nameBuilder.createSegmentName("a813c"), equalTo("seg256-a8")); + assertThat(nameBuilder.createSegmentName("b4d01"), equalTo("seg256-b4")); + assertThat(nameBuilder.createSegmentName("7122f"), equalTo("seg256-71")); + } +} From 62368b1cdab9513f48cff2a0d5c0500b08e34314 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 13 Mar 2017 20:34:55 +0100 Subject: [PATCH 087/125] 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 70c26d8bc..689c303d8 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 c926a9129..dfc510875 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 84acc4557a1cc9fdf0db840cb5af1ea7b44ad19a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 14 Mar 2017 20:46:28 +0100 Subject: [PATCH 088/125] #1125 Limbo persistence: convert old segments, add tests - On startup / reload the playerdata folder is scanned for old segment files, whose data is migrated before they are deleted - Add tests for segment files persistence holder --- .../limbo/persistence/LimboPersistence.java | 11 +- .../persistence/LimboPersistenceType.java | 4 + .../persistence/SegmentConfiguration.java | 4 +- .../SegmentFilesPersistenceHolder.java | 124 +++++++++-- .../limbo/persistence/SegmentNameBuilder.java | 17 +- .../settings/properties/LimboSettings.java | 6 +- .../data/limbo/LimboPlayerMatchers.java | 112 ++++++++++ .../persistence/LimboPersistenceTest.java | 17 -- .../persistence/LimboPersistenceTypeTest.java | 48 ++++ .../persistence/SegmentConfigurationTest.java | 10 +- .../SegmentFilesPersistenceHolderTest.java | 205 ++++++++++++++++++ .../persistence/SegmentNameBuilderTest.java | 12 - .../authme/data/limbo/seg16-8-limbo.json | 32 +++ .../authme/data/limbo/seg16-f-limbo.json | 17 ++ .../authme/data/limbo/seg32-01011-limbo.json | 1 + .../authme/data/limbo/seg32-10110-limbo.json | 17 ++ 16 files changed, 574 insertions(+), 63 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java create mode 100644 src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java create mode 100644 src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json create mode 100644 src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json create mode 100644 src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json create mode 100644 src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java index 35cefb486..f07e82cd3 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java @@ -70,13 +70,10 @@ public class LimboPersistence implements SettingsDependent { @Override public void reload(Settings settings) { LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE); - if (handler == null || handler.getType() != persistenceType) { - // If we're changing from an existing handler, output a quick hint that nothing is converted. - if (handler != null) { - ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted."); - } - - handler = handlerFactory.newInstance(persistenceType.getImplementationClass()); + // If we're changing from an existing handler, output a quick hint that nothing is converted. + if (handler != null && handler.getType() != persistenceType) { + ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted."); } + handler = handlerFactory.newInstance(persistenceType.getImplementationClass()); } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java index c998f95a4..68b4611bc 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java @@ -5,12 +5,16 @@ package fr.xephi.authme.data.limbo.persistence; */ public enum LimboPersistenceType { + /** Store each LimboPlayer in a separate file. */ INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class), + /** Store all LimboPlayers in the same file. */ SINGLE_FILE(SingleFilePersistenceHandler.class), + /** Distribute LimboPlayers by segments into a set number of files. */ SEGMENT_FILES(SegmentFilesPersistenceHolder.class), + /** No persistence to disk. */ DISABLED(NoOpPersistenceHandler.class); private final Class implementationClass; diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java index d2f0d202a..5053ba521 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java @@ -38,8 +38,8 @@ public enum SegmentConfiguration { /** 1. */ ONE(1, 1), - /** 2. */ - TWO(2, 1), + ///** 2. */ + //TWO(2, 1), /** 4. */ FOUR(4, 1), diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java index 24751fe0c..e786ca482 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java @@ -17,11 +17,14 @@ import org.bukkit.entity.Player; import javax.inject.Inject; import java.io.File; import java.io.FileWriter; -import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; /** * Persistence handler for LimboPlayer objects by distributing the objects to store @@ -51,7 +54,8 @@ class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION)); - // TODO #1125: Check for other segment files and attempt to convert? + convertOldDataToCurrentSegmentScheme(); + deleteEmptyFiles(); } @Override @@ -94,16 +98,16 @@ class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { } } + @Override + public LimboPersistenceType getType() { + return LimboPersistenceType.SEGMENT_FILES; + } + private void saveEntries(Map entries, File file) { - if (entries.isEmpty()) { - // TODO #1125: Probably should do a sweep of empty files on startup / shutdown, but not all the time - FileUtils.delete(file); - } else { - try (FileWriter fw = new FileWriter(file)) { - gson.toJson(entries, fw); - } catch (IOException e) { - ConsoleLogger.logException("Could not write to '" + file + "':", e); - } + try (FileWriter fw = new FileWriter(file)) { + gson.toJson(entries, fw); + } catch (Exception e) { + ConsoleLogger.logException("Could not write to '" + file + "':", e); } } @@ -114,7 +118,7 @@ class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { try { return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE); - } catch (IOException e) { + } catch (Exception e) { ConsoleLogger.logException("Failed reading '" + file + "':", e); } return null; @@ -125,8 +129,98 @@ class SegmentFilesPersistenceHolder implements LimboPersistenceHandler { return new File(cacheFolder, segment + "-limbo.json"); } - @Override - public LimboPersistenceType getType() { - return LimboPersistenceType.SINGLE_FILE; + /** + * Loads segment files in the cache folder that don't correspond to the current segmenting scheme + * and migrates the data into files of the current segments. This allows a player to change the + * segment size without any loss of data. + */ + private void convertOldDataToCurrentSegmentScheme() { + String currentPrefix = segmentNameBuilder.getPrefix(); + File[] files = listFiles(cacheFolder); + Map allLimboPlayers = new HashMap<>(); + List migratedFiles = new ArrayList<>(); + + for (File file : files) { + if (isLimboJsonFile(file) && !file.getName().startsWith(currentPrefix)) { + Map data = readLimboPlayers(file); + if (data != null) { + allLimboPlayers.putAll(data); + migratedFiles.add(file); + } + } + } + + if (!allLimboPlayers.isEmpty()) { + saveToNewSegments(allLimboPlayers); + migratedFiles.forEach(FileUtils::delete); + } + } + + /** + * Saves the LimboPlayer data read from old segmenting schemes into the current segmenting scheme. + * + * @param limbosFromOldSegments the limbo players to store into the current segment files + */ + private void saveToNewSegments(Map limbosFromOldSegments) { + Map> limboBySegment = groupBySegment(limbosFromOldSegments); + + ConsoleLogger.info("Saving " + limbosFromOldSegments.size() + " LimboPlayers from old segments into " + + limboBySegment.size() + " current segments"); + for (Map.Entry> entry : limboBySegment.entrySet()) { + File file = new File(cacheFolder, entry.getKey() + "-limbo.json"); + Map limbosToSave = Optional.ofNullable(readLimboPlayers(file)) + .orElseGet(HashMap::new); + limbosToSave.putAll(entry.getValue()); + saveEntries(limbosToSave, file); + } + } + + /** + * Converts a Map of UUID to LimboPlayers to a 2-dimensional Map of LimboPlayers by segment ID and UUID. + * {@code Map(uuid -> LimboPlayer) to Map(segment -> Map(uuid -> LimboPlayer))} + * + * @param readLimboPlayers the limbo players to order by segment + * @return limbo players ordered by segment ID and associated player UUID + */ + private Map> groupBySegment(Map readLimboPlayers) { + Map> limboBySegment = new HashMap<>(); + for (Map.Entry entry : readLimboPlayers.entrySet()) { + String segmentId = segmentNameBuilder.createSegmentName(entry.getKey()); + limboBySegment.computeIfAbsent(segmentId, s -> new HashMap<>()) + .put(entry.getKey(), entry.getValue()); + } + return limboBySegment; + } + + /** + * Deletes files from the current segmenting scheme that are empty. + */ + private void deleteEmptyFiles() { + File[] files = listFiles(cacheFolder); + + long deletedFiles = Arrays.stream(files) + // typically the size is 2 because there's an empty JSON map: {} + .filter(f -> isLimboJsonFile(f) && f.length() < 3) + .peek(FileUtils::delete) + .count(); + ConsoleLogger.debug("Limbo: Deleted {0} empty segment files", deletedFiles); + } + + /** + * @param file the file to check + * @return true if it is a segment file storing Limbo data, false otherwise + */ + private static boolean isLimboJsonFile(File file) { + String name = file.getName(); + return name.startsWith("seg") && name.endsWith("-limbo.json"); + } + + private static File[] listFiles(File folder) { + File[] files = folder.listFiles(); + if (files == null) { + ConsoleLogger.warning("Could not get files of '" + folder + "'"); + return new File[0]; + } + return files; } } diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java index df517fbad..52e1141bf 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java @@ -25,15 +25,28 @@ class SegmentNameBuilder { this.charToSegmentChar = buildCharMap(distribution); } + /** + * Returns the segment ID for the given UUID. + * + * @param uuid the player's uuid to get the segment for + * @return id the uuid belongs to + */ String createSegmentName(String uuid) { if (distribution == 16) { return prefix + uuid.substring(0, length); } else { - return prefix + createSegmentName(uuid.substring(0, length).toCharArray()); + return prefix + buildSegmentName(uuid.substring(0, length).toCharArray()); } } - private String createSegmentName(char[] chars) { + /** + * @return the prefix used for the current segment configuration + */ + String getPrefix() { + return prefix; + } + + private String buildSegmentName(char[] chars) { if (chars.length == 1) { return String.valueOf(charToSegmentChar.get(chars[0])); } diff --git a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java index d8d7104e7..a48db6b36 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java @@ -34,14 +34,14 @@ public final class LimboSettings implements SettingsHolder { "This setting only affects SEGMENT_FILES persistence. The segment file", "persistence attempts to reduce the number of files by distributing players into various", "buckets based on their UUID. This setting defines into how many files the players should", - "be distributed. Possible values: ONE, TWO, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,", + "be distributed. Possible values: ONE, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,", "ONE_TWENTY for 128, TWO_FIFTY for 256.", "For example, if you expect 100 non-logged in players, setting to SIXTEEN will average", "6.25 players per file (100 / 16). If you set to ONE, like persistence SINGLE_FILE, only", "one file will be used. Contrary to SINGLE_FILE, it won't keep the entries in cache, which", "may deliver different results in terms of performance.", - "Note: if you change this setting you lose all stored LimboPlayer data because the", - "distribution of players will be different." + "Note: if you change this setting all data will be migrated. If you have a lot of data,", + "change this setting only on server restart, not with /authme reload." }) public static final Property SEGMENT_DISTRIBUTION = newProperty(SegmentConfiguration.class, "limbo.persistence.segmentDistribution", SegmentConfiguration.SIXTEEN); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java new file mode 100644 index 000000000..4c4793a34 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerMatchers.java @@ -0,0 +1,112 @@ +package fr.xephi.authme.data.limbo; + +import org.bukkit.Location; +import org.bukkit.World; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static java.lang.String.format; + +/** + * Contains matchers for LimboPlayer. + */ +public final class LimboPlayerMatchers { + + private LimboPlayerMatchers() { + } + + public static Matcher isLimbo(LimboPlayer limbo) { + return isLimbo(limbo.isOperator(), limbo.getGroup(), limbo.isCanFly(), + limbo.getWalkSpeed(), limbo.getFlySpeed()); + } + + public static Matcher isLimbo(boolean isOp, String group, boolean canFly, + float walkSpeed, float flySpeed) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + return item.isOperator() == isOp && item.getGroup().equals(group) && item.isCanFly() == canFly + && walkSpeed == item.getWalkSpeed() && flySpeed == item.getFlySpeed(); + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with isOp=%s, group=%s, canFly=%s, walkSpeed=%f, flySpeed=%f", + isOp, group, canFly, walkSpeed, flySpeed)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + description.appendText(format("Limbo with isOp=%s, group=%s, canFly=%s, walkSpeed=%f, flySpeed=%f", + item.isOperator(), item.getGroup(), item.isCanFly(), item.getWalkSpeed(), item.getFlySpeed())); + } + }; + } + + public static Matcher hasLocation(String world, double x, double y, double z) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + Location location = item.getLocation(); + return location.getWorld().getName().equals(world) + && location.getX() == x && location.getY() == y && location.getZ() == z; + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f", + world, x, y, z)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + Location location = item.getLocation(); + if (location == null) { + description.appendText("Limbo with location = null"); + } else { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f", + location.getWorld().getName(), location.getX(), location.getY(), location.getZ())); + } + } + }; + } + + public static Matcher hasLocation(World world, double x, double y, double z) { + return hasLocation(world.getName(), x, y, z); + } + + public static Matcher hasLocation(String world, double x, double y, double z, float yaw, float pitch) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(LimboPlayer item) { + Location location = item.getLocation(); + return hasLocation(location.getWorld(), location.getX(), location.getY(), location.getZ()).matches(item) + && location.getYaw() == yaw && location.getPitch() == pitch; + } + + @Override + public void describeTo(Description description) { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f, yaw=%f, pitch=%f", + world, x, y, z, yaw, pitch)); + } + + @Override + public void describeMismatchSafely(LimboPlayer item, Description description) { + Location location = item.getLocation(); + if (location == null) { + description.appendText("Limbo with location = null"); + } else { + description.appendText(format("Limbo with location: world=%s, x=%f, y=%f, z=%f, yaw=%f, pitch=%f", + location.getWorld().getName(), location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch())); + } + } + }; + } + + public static Matcher hasLocation(Location location) { + return hasLocation(location.getWorld().getName(), location.getX(), location.getY(), location.getZ(), + location.getYaw(), location.getPitch()); + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java index 3511771dd..d253869f7 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java @@ -15,7 +15,6 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import java.util.logging.Logger; @@ -33,7 +32,6 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.hamcrest.MockitoHamcrest.argThat; /** @@ -103,21 +101,6 @@ public class LimboPersistenceTest { assertThat(getHandler(), instanceOf(LimboPersistenceType.INDIVIDUAL_FILES.getImplementationClass())); } - @Test - public void shouldNotReinitializeHandlerForSameType() { - // given - LimboPersistenceHandler currentHandler = getHandler(); - Mockito.reset(handlerFactory); - given(currentHandler.getType()).willCallRealMethod(); - - // when - limboPersistence.reload(settings); - - // then - verifyZeroInteractions(handlerFactory); - assertThat(currentHandler, sameInstance(getHandler())); - } - @Test public void shouldHandleExceptionWhenGettingLimbo() { // given diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java new file mode 100644 index 000000000..248ab9c14 --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTypeTest.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.data.limbo.persistence; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link LimboPersistenceType}. + */ +public class LimboPersistenceTypeTest { + + @Test + public void shouldHaveUniqueImplementationClasses() { + // given + Set> classes = new HashSet<>(); + + // when / then + for (LimboPersistenceType persistenceType : LimboPersistenceType.values()) { + if (!classes.add(persistenceType.getImplementationClass())) { + fail("Implementation class '" + persistenceType.getImplementationClass() + "' from '" + + persistenceType + "' already encountered previously"); + } + } + } + + @Test + public void shouldHaveTypeReturnedFromImplementationClass() { + for (LimboPersistenceType persistenceType : LimboPersistenceType.values()) { + // given + LimboPersistenceHandler implementationMock = mock(persistenceType.getImplementationClass()); + given(implementationMock.getType()).willCallRealMethod(); + + // when + LimboPersistenceType returnedType = implementationMock.getType(); + + // then + assertThat(returnedType, equalTo(persistenceType)); + } + } + +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java index f5e5e1241..34d23723d 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentConfigurationTest.java @@ -32,16 +32,16 @@ public class SegmentConfigurationTest { @Test public void shouldHaveDifferentSegmentSizes() { // given - Set sizes = new HashSet<>(); + Set segmentTotals = new HashSet<>(); // when / then for (SegmentConfiguration entry : SegmentConfiguration.values()) { - int segSize = (int) Math.pow(entry.getDistribution(), entry.getLength()); + int totalSegments = entry.getTotalSegments(); assertThat(entry + " must have a positive segment size", - segSize, greaterThan(0)); + totalSegments, greaterThan(0)); - assertThat(entry + " has a segment size that was already encountered (" + segSize + ")", - sizes.add(segSize), equalTo(true)); + assertThat(entry + " has a segment total that was already encountered (" + totalSegments + ")", + segmentTotals.add(totalSegments), equalTo(true)); } } } diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java new file mode 100644 index 000000000..064fc9c3d --- /dev/null +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolderTest.java @@ -0,0 +1,205 @@ +package fr.xephi.authme.data.limbo.persistence; + +import ch.jalu.injector.testing.BeforeInjecting; +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.data.limbo.LimboPlayer; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.LimboSettings; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.hamcrest.Matcher; +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.Mock; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import static fr.xephi.authme.data.limbo.LimboPlayerMatchers.hasLocation; +import static fr.xephi.authme.data.limbo.LimboPlayerMatchers.isLimbo; +import static java.util.UUID.fromString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Test for {@link SegmentFilesPersistenceHolder}. + */ +@RunWith(DelayedInjectionRunner.class) +public class SegmentFilesPersistenceHolderTest { + + /** Player is in seg32-10110 and should be migrated into seg16-f. */ + private static final UUID MIGRATED_UUID = fromString("f6a97c88-7c8f-c12e-4931-6206d4ca067d"); + private static final Matcher MIGRATED_LIMBO_MATCHER = + isLimbo(false, "noob", true, 0.2f, 0.1f); + + /** Existing player in seg16-f. */ + private static final UUID UUID_FAB69 = fromString("fab69c88-2cd0-1fed-f00d-dead14ca067d"); + private static final Matcher FAB69_MATCHER = + isLimbo(false, "", false, 0.2f, 0.1f); + + /** Player in seg16-8. */ + private static final UUID UUID_STAFF = fromString("88897c88-7c8f-c12e-4931-6206d4ca067d"); + private static final Matcher STAFF_MATCHER = + isLimbo(true, "staff", false, 0.3f, 0.1f); + + /** Player in seg16-8. */ + private static final UUID UUID_8C679 = fromString("8c679491-1234-abcd-9102-1fa6e0cc3f81"); + private static final Matcher SC679_MATCHER = + isLimbo(false, "primary", true, 0.1f, 0.0f); + + /** UUID for which no data is stored (belongs to a segment file that does not exist, seg16-4). */ + private static final UUID UNKNOWN_UUID = fromString("42d1cc0b-8f12-d04a-e7ba-a067d05cdc39"); + + /** UUID for which no data is stored (belongs to an existing segment file: seg16-8). */ + private static final UUID UNKNOWN_UUID2 = fromString("84d1cc0b-8f12-d04a-e7ba-a067d05cdc39"); + + + @InjectDelayed + private SegmentFilesPersistenceHolder persistenceHandler; + + @Mock + private Settings settings; + @Mock + private BukkitService bukkitService; + @DataFolder + private File dataFolder; + private File playerDataFolder; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void initLogger() { + TestHelper.setupLogger(); + } + + @BeforeInjecting + public void setUpClasses() throws IOException { + given(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION)).willReturn(SegmentConfiguration.SIXTEEN); + dataFolder = temporaryFolder.newFolder(); + playerDataFolder = new File(dataFolder, "playerdata"); + playerDataFolder.mkdir(); + + File limboFilesFolder = new File("src/test/resources/fr/xephi/authme/data/limbo"); + for (File file : limboFilesFolder.listFiles()) { + File from = new File(playerDataFolder, file.getName()); + Files.copy(file, from); + } + + given(bukkitService.getWorld(anyString())) + .willAnswer(invocation -> { + World world = mock(World.class); + given(world.getName()).willReturn(invocation.getArgument(0)); + return world; + }); + } + + // Note ljacqu 20170314: These tests are a little slow to set up; therefore we sometimes + // test things in one test that would traditionally belong into two separate tests + + @Test + public void shouldMigrateOldSegmentFilesOnStartup() { + // Ensure that only the files of the current segmenting scheme remain + assertThat(playerDataFolder.list(), arrayContainingInAnyOrder("seg16-8-limbo.json", "seg16-f-limbo.json")); + + // Check that the expected limbo players can be read + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(MIGRATED_UUID)), MIGRATED_LIMBO_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_FAB69)), FAB69_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_STAFF)), STAFF_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + + // Check that unknown players are null (whose segment file exists and does not exist) + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID)), nullValue()); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID2)), nullValue()); + } + + @Test + public void shouldRemovePlayer() { + // given + Player playerToRemove = mockPlayerWithUuid(UUID_STAFF); + Player unknownPlayerToRemove = mockPlayerWithUuid(UNKNOWN_UUID); + + // when + persistenceHandler.removeLimboPlayer(playerToRemove); + persistenceHandler.removeLimboPlayer(unknownPlayerToRemove); + + // then + assertThat(persistenceHandler.getLimboPlayer(playerToRemove), nullValue()); + assertThat(persistenceHandler.getLimboPlayer(unknownPlayerToRemove), nullValue()); + // Player in same segment should still exist... + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + + // Check that we didn't create seg16-4 by deleting UNKNOWN_UUID. + assertThat(playerDataFolder.list(), arrayContainingInAnyOrder("seg16-8-limbo.json", "seg16-f-limbo.json")); + } + + @Test + public void shouldAddPlayer() { + // given + Player uuidToAdd1 = mockPlayerWithUuid(UNKNOWN_UUID); + Location location1 = new Location(mockWorldWithName("1world"), 120, 60, -80, 0.42345f, 120.32f); + LimboPlayer limbo1 = new LimboPlayer(location1, false, "group-1", true, 0.1f, 0.2f); + Player uuidToAdd2 = mockPlayerWithUuid(UNKNOWN_UUID2); + Location location2 = new Location(mockWorldWithName("2world"), -40, 20, 33, 4.235f, 8.32299f); + LimboPlayer limbo2 = new LimboPlayer(location2, true, "", false, 0.0f, 0.25f); + + // when + persistenceHandler.saveLimboPlayer(uuidToAdd1, limbo1); + persistenceHandler.saveLimboPlayer(uuidToAdd2, limbo2); + + // then + LimboPlayer addedPlayer1 = persistenceHandler.getLimboPlayer(uuidToAdd1); + assertThat(addedPlayer1, isLimbo(limbo1)); + assertThat(addedPlayer1, hasLocation(location1)); + LimboPlayer addedPlayer2 = persistenceHandler.getLimboPlayer(uuidToAdd2); + assertThat(addedPlayer2, isLimbo(limbo2)); + assertThat(addedPlayer2, hasLocation(location2)); + + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(MIGRATED_UUID)), MIGRATED_LIMBO_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_FAB69)), FAB69_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_STAFF)), STAFF_MATCHER); + assertThat(persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UUID_8C679)), SC679_MATCHER); + } + + @Test + public void shouldHandleReadErrorGracefully() throws IOException { + // given + // assumption + File invalidFile = new File(playerDataFolder, "seg16-4-limbo.json"); + assertThat(invalidFile.exists(), equalTo(false)); + Files.write("not valid json".getBytes(), invalidFile); + + // when + LimboPlayer result = persistenceHandler.getLimboPlayer(mockPlayerWithUuid(UNKNOWN_UUID)); + + // then + assertThat(result, nullValue()); + } + + private static Player mockPlayerWithUuid(UUID uuid) { + Player player = mock(Player.class); + given(player.getUniqueId()).willReturn(uuid); + return player; + } + + private static World mockWorldWithName(String name) { + World world = mock(World.class); + given(world.getName()).willReturn(name); + return world; + } +} diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java index 41c3b0ffe..64843db85 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilderTest.java @@ -11,7 +11,6 @@ import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.ONE; import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTEEN; import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.SIXTY_FOUR; import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.THIRTY_TWO; -import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.TWO; import static fr.xephi.authme.data.limbo.persistence.SegmentConfiguration.TWO_FIFTY; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -73,17 +72,6 @@ public class SegmentNameBuilderTest { assertThat(nameBuilder.createSegmentName("329"), equalTo("seg1-0")); } - @Test - public void shouldCreateTwoSegments() { - // given - SegmentNameBuilder nameBuilder = new SegmentNameBuilder(TWO); - - // when / then - assertThat(nameBuilder.createSegmentName("f6c"), equalTo("seg2-1")); - assertThat(nameBuilder.createSegmentName("29f"), equalTo("seg2-0")); - assertThat(nameBuilder.createSegmentName("983"), equalTo("seg2-1")); - } - @Test public void shouldCreateFourSegments() { // given diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json new file mode 100644 index 000000000..84da8a268 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg16-8-limbo.json @@ -0,0 +1,32 @@ +{ + "88897c88-7c8f-c12e-4931-6206d4ca067d": { + "location": { + "world": "world", + "x": -196.69999998807907, + "y": 67.0, + "z": 5.699999988079071, + "yaw": 222.14977, + "pitch": 10.649977 + }, + "group": "staff", + "operator": true, + "can-fly": false, + "walk-speed": 0.3, + "fly-speed": 0.1 + }, + "8c679491-1234-abcd-9102-1fa6e0cc3f81": { + "location": { + "world": "nether", + "x": 300.12345, + "y": 42.3, + "z": -72.482749988079071, + "yaw": 100.27788, + "pitch": 4.242111 + }, + "group": "primary", + "operator": false, + "can-fly": true, + "walk-speed": 0.1, + "fly-speed": 0.0 + } +} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json new file mode 100644 index 000000000..9f2562622 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg16-f-limbo.json @@ -0,0 +1,17 @@ +{ + "fab69c88-2cd0-1fed-f00d-dead14ca067d": { + "location": { + "world": "world", + "x": -196.69999998807907, + "y": 67.0, + "z": 5.699999988079071, + "yaw": 222.14977, + "pitch": 10.649977 + }, + "group": "", + "operator": false, + "can-fly": false, + "walk-speed": 0.2, + "fly-speed": 0.1 + } +} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg32-01011-limbo.json @@ -0,0 +1 @@ +{} diff --git a/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json b/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json new file mode 100644 index 000000000..b50fb8bda --- /dev/null +++ b/src/test/resources/fr/xephi/authme/data/limbo/seg32-10110-limbo.json @@ -0,0 +1,17 @@ +{ + "f6a97c88-7c8f-c12e-4931-6206d4ca067d": { + "location": { + "world": "lobby", + "x": -120.31415, + "y": 25.0, + "z": -80.71234, + "yaw": 22.14977, + "pitch": 40.649977 + }, + "group": "noob", + "operator": false, + "can-fly": true, + "walk-speed": 0.2, + "fly-speed": 0.1 + } +} From 1da74cb987c0c8c472d0373f2a730eea549c430a Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 14 Mar 2017 22:26:19 +0100 Subject: [PATCH 089/125] #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 3dcfe8e04..73bef42ab 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 dd1f6e51e..9511c6e33 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 5a007b32d..40de8ca69 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 5e0696af9..1f200c0f0 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 21ffa9525..3953d4efd 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 629adbd40..7111f81b8 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 2214fa5839cf19fedec292f09a6edc479eafd7f1 Mon Sep 17 00:00:00 2001 From: Gnat008 Date: Tue, 14 Mar 2017 18:26:32 -0400 Subject: [PATCH 090/125] Implement /email setpassword --- docs/config.md | 8 +- .../authme/command/CommandInitializer.java | 12 +++ .../executable/email/SetPasswordCommand.java | 53 +++++++++ .../fr/xephi/authme/message/MessageKey.java | 6 ++ .../service/PasswordRecoveryService.java | 26 +++++ .../settings/properties/SecuritySettings.java | 6 ++ src/main/resources/messages/messages_bg.yml | 2 + src/main/resources/messages/messages_br.yml | 2 + src/main/resources/messages/messages_cz.yml | 2 + src/main/resources/messages/messages_de.yml | 2 + src/main/resources/messages/messages_en.yml | 2 + src/main/resources/messages/messages_es.yml | 2 + src/main/resources/messages/messages_eu.yml | 2 + src/main/resources/messages/messages_fi.yml | 2 + src/main/resources/messages/messages_fr.yml | 2 + src/main/resources/messages/messages_gl.yml | 2 + src/main/resources/messages/messages_hu.yml | 2 + src/main/resources/messages/messages_id.yml | 2 + src/main/resources/messages/messages_it.yml | 2 + src/main/resources/messages/messages_ko.yml | 2 + src/main/resources/messages/messages_lt.yml | 2 + src/main/resources/messages/messages_nl.yml | 2 + src/main/resources/messages/messages_pl.yml | 2 + src/main/resources/messages/messages_pt.yml | 2 + src/main/resources/messages/messages_ro.yml | 2 + src/main/resources/messages/messages_ru.yml | 2 + src/main/resources/messages/messages_sk.yml | 2 + src/main/resources/messages/messages_tr.yml | 2 + src/main/resources/messages/messages_uk.yml | 2 + src/main/resources/messages/messages_vn.yml | 2 + src/main/resources/messages/messages_zhcn.yml | 2 + src/main/resources/messages/messages_zhhk.yml | 2 + src/main/resources/messages/messages_zhmc.yml | 2 + src/main/resources/messages/messages_zhtw.yml | 2 + .../email/SetPasswordCommandTest.java | 102 ++++++++++++++++++ 35 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java diff --git a/docs/config.md b/docs/config.md index 51c917032..4321aea88 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, @@ -444,6 +444,10 @@ Security: validForHours: 4 # Max number of tries to enter recovery code maxTries: 3 + # How long a player has after password recovery to change their password + # without logging in. This is in minutes. + # Default: 2 minutes + passwordChangeTimeout: 2 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. @@ -464,4 +468,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 Mon Mar 06 13:51:04 EST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Mar 14 16:42:26 EDT 2017 diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 6758c0086..663155637 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -32,6 +32,7 @@ import fr.xephi.authme.command.executable.email.ChangeEmailCommand; import fr.xephi.authme.command.executable.email.EmailBaseCommand; import fr.xephi.authme.command.executable.email.ProcessCodeCommand; import fr.xephi.authme.command.executable.email.RecoverEmailCommand; +import fr.xephi.authme.command.executable.email.SetPasswordCommand; import fr.xephi.authme.command.executable.email.ShowEmailCommand; import fr.xephi.authme.command.executable.login.LoginCommand; import fr.xephi.authme.command.executable.logout.LogoutCommand; @@ -435,6 +436,17 @@ public class CommandInitializer { .executableCommand(ProcessCodeCommand.class) .register(); + // Register the change password after recovery command + CommandDescription.builder() + .parent(EMAIL_BASE) + .labels("setpassword") + .description("Set new password after recovery") + .detailedDescription("Set a new password after successfully recovering your account.") + .withArgument("password", "New password", false) + .permission(PlayerPermission.RECOVER_EMAIL) + .executableCommand(SetPasswordCommand.class) + .register(); + // Register the base captcha command CommandDescription CAPTCHA_BASE = CommandDescription.builder() .parent(null) diff --git a/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java new file mode 100644 index 000000000..eafea6df3 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java @@ -0,0 +1,53 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.PasswordRecoveryService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.service.ValidationService.ValidationResult; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +/** + * Command for changing password following successful recovery. + */ +public class SetPasswordCommand extends PlayerCommand { + + @Inject + private DataSource dataSource; + + @Inject + private CommonService commonService; + + @Inject + private PasswordRecoveryService recoveryService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private ValidationService validationService; + + @Override + protected void runCommand(Player player, List arguments) { + if (recoveryService.canChangePassword(player)) { + String name = player.getName(); + String password = arguments.get(0); + + ValidationResult result = validationService.validatePassword(password, name); + if (!result.hasError()) { + HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); + dataSource.updatePassword(name, hashedPassword); + commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + } else { + commonService.send(player, result.getMessageKey(), result.getArgs()); + } + } + } +} diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 36c4a9cda..961ec1949 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -230,6 +230,12 @@ public enum MessageKey { /** You have exceeded the maximum number of attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one. */ RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"), + /** Please use the command /email setpassword to change your password immediately. */ + RECOVERY_CHANGE_PASSWORD("recovery_change_password"), + + /** You cannot change your password using this command anymore. */ + CHANGE_PASSWORD_EXPIRED("change_password_expired"), + /** An email was already sent recently. You must wait %time before you can send a new one. */ EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"), diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java index 272f6a952..79e5beec2 100644 --- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -8,6 +8,7 @@ import fr.xephi.authme.message.Messages; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.properties.SecuritySettings; +import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.util.expiring.Duration; import fr.xephi.authme.util.expiring.ExpiringSet; @@ -46,11 +47,14 @@ public class PasswordRecoveryService implements Reloadable { private Messages messages; private ExpiringSet emailCooldown; + private ExpiringSet successfulRecovers; @PostConstruct private void initEmailCooldownSet() { emailCooldown = new ExpiringSet<>( commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); + successfulRecovers = new ExpiringSet<>( + commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES); } /** @@ -96,6 +100,11 @@ public class PasswordRecoveryService implements Reloadable { if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); emailCooldown.add(player.getName().toLowerCase()); + + String address = PlayerUtils.getPlayerIp(player); + + successfulRecovers.add(address); + commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD); } else { commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); } @@ -117,6 +126,23 @@ public class PasswordRecoveryService implements Reloadable { return true; } + /** + * Checks if a player can change their password after recovery + * using the /email setpassword command. + * + * @param player The player to check. + * @return True if the player can change their password. + */ + public boolean canChangePassword(Player player) { + String address = PlayerUtils.getPlayerIp(player); + Duration waitDuration = successfulRecovers.getExpiration(address); + if (waitDuration.getDuration() > 0) { + messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR); + return false; + } + return true; + } + @Override public void reload() { emailCooldown.setExpiration( 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 0aa517aeb..d16a1c565 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java @@ -118,6 +118,12 @@ public class SecuritySettings implements SettingsHolder { public static final Property RECOVERY_CODE_MAX_TRIES = newProperty("Security.recoveryCode.maxTries", 3); + @Comment({"How long a player has after password recovery to change their password", + "without logging in. This is in minutes.", + "Default: 2 minutes"}) + public static final Property PASSWORD_CHANGE_TIMEOUT = + newProperty("Security.recoveryCode.passwordChangeTimeout", 2); + @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." diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index c4042804a..fdaf32fac 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -47,6 +47,7 @@ unregistered: '&cУспешно от-регистриран!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' usage_unreg: '&cКоманда: /unregister парола' pwd_changed: '&cПаролата е променена!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index e39c553c3..823c1cd84 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -51,6 +51,7 @@ recovery_code_sent: 'Um código de recuperação para redefinir sua senha foi en # TODO: Missing tags %count recovery_code_incorrect: 'O código de recuperação esta incorreto! Use /email recovery [email] para gerar um novo!' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cA sua conta ainda não está ativada, por favor, verifique seus e-mails!' usage_unreg: '&cUse: /unregister ' pwd_changed: '&2Senha alterada com sucesso!' @@ -92,6 +93,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index fb8ef0f8b..0e6503fa3 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Kód pro obnovení hesla byl odeslán na váš email.' # TODO: Missing tags %count recovery_code_incorrect: 'Kód pro není správný! Použijte příkaz /email recovery [email] pro vygenerování nového.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cTvůj účet není aktivovaný, zkontroluj si svůj E-mail.' usage_unreg: '&cPoužij: "/unregister TvojeHeslo".' pwd_changed: '&cHeslo změněno!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 02fcfad49..65b3f79c2 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Ein Wiederherstellungscode zum Zurücksetzen deines Passwor # TODO: Missing tags %count recovery_code_incorrect: 'Der Wiederherstellungscode stimmt nicht! Nutze /email recovery [email] um einen neuen zu generieren.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cDein Account wurde noch nicht aktiviert. Bitte prüfe deine E-Mails!' usage_unreg: '&cBenutze: /unregister ' pwd_changed: '&2Passwort geändert!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index cf22eb0b3..d1f83d2ce 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -46,6 +46,7 @@ two_factor_create: '&2Your secret code is %code. You can scan it from here %url' recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' recovery_code_incorrect: 'The recovery code is not correct! You have %count tries remaining.' recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!' usage_unreg: '&cUsage: /unregister ' pwd_changed: '&2Password changed successfully!' @@ -87,6 +88,7 @@ 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 ' +change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index 6c6508057..96c0f6a10 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -50,6 +50,7 @@ recovery_code_sent: 'El código de recuperación para recuperar tu contraseña s # TODO: Missing tags %count recovery_code_incorrect: '¡El código de recuperación no es correcto! Usa "/email recovery [email]" para generar uno nuevo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fTu cuenta no está activada aún, ¡revisa tu correo!' usage_unreg: '&cUso: /unregister contraseña' pwd_changed: '&c¡Contraseña cambiada!' @@ -91,6 +92,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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 648d7f209..0e9e9f8b4 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -47,6 +47,7 @@ unregistered: '&cZure erregistroa ezabatu duzu!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fZure kontua aktibatu gabe dago, konfirmatu zure emaila!' usage_unreg: '&cErabili: /unregister password' pwd_changed: '&cPasahitza aldatu duzu!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index e3a44396e..81861b3b5 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -47,6 +47,7 @@ unregistered: '&cPelaajatili poistettu onnistuneesti!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fKäyttäjäsi ei ole vahvistettu!' usage_unreg: '&cKäyttötapa: /unregister password' pwd_changed: '&cSalasana vaihdettu!!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 9a5b6dcad..0c456dcc2 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -52,6 +52,7 @@ recovery_code_sent: 'Un code de récupération a été envoyé à votre adresse # TODO: Missing tags %count recovery_code_incorrect: '&cLe code de réinitialisation est incorrect!%nl%Faites "/email recovery [email]" pour en générer un nouveau.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fCe compte n''est pas actif, consultez vos emails !' usage_unreg: '&cPour supprimer votre compte, utilisez "/unregister "' pwd_changed: '&aMot de passe changé avec succès !' @@ -93,6 +94,7 @@ email_send_failure: '&cL''email n''a pas pu être envoyé. Veuillez contacter un 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 "' +# TODO change_password_expired: 'You cannot change your password using this command anymore.' 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 diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index da7270bc0..235780fcc 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -47,6 +47,7 @@ unregistered: '&cFeito! Xa non estás rexistrado!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' 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!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index a77dfd6c2..de9677ef9 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'A jelszavad visszaállításához szükséges kódot sikere # TODO: Missing tags %count recovery_code_incorrect: 'A visszaállító kód helytelen volt! Használd a következő parancsot: /email recovery [email címed] egy új generálásához' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cA felhasználód aktiválása még nem történt meg, ellenőrizd a megadott emailed!' usage_unreg: '&cHasználat: "/unregister "' pwd_changed: '&cJelszó sikeresen megváltoztatva!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index c5c9caf67..da720ad3f 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -47,6 +47,7 @@ unregistered: '&cUnregister berhasil!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cAkunmu belum diaktifkan, silahkan periksa email kamu!' # TODO usage_unreg: '&cUsage: /unregister ' pwd_changed: '&2Berhasil mengubah password!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 246fa3bce..2e8f95019 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -49,6 +49,7 @@ recovery_code_sent: 'Una email contenente il codice di recupero per reimpostare # TODO: Missing tags %count recovery_code_incorrect: 'Il codice di recupero inserito non è corretto! Scrivi "/email recovery " per generarne uno nuovo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cIl tuo account non è stato ancora verificato, controlla fra le tue email per scoprire come attivarlo!' usage_unreg: '&cUtilizzo: /unregister ' pwd_changed: '&2Password cambiata correttamente!' @@ -90,6 +91,7 @@ email_send_failure: 'Non è stato possibile inviare l''email di recupero. Per fa 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 change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cUna email di recupero ti è già stata inviata recentemente. Devi attendere %time prima di poterne richiedere una nuova.' # Captcha diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index cfb6fcb57..25f369d0b 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -51,6 +51,7 @@ unregistered: '&c성공적으로 탈퇴했습니다!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!' usage_unreg: '&c사용법: /unregister 비밀번호' pwd_changed: '&c비밀번호를 변경했습니다!' @@ -92,6 +93,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index 8c144a808..b6da0c76c 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -47,6 +47,7 @@ unregistered: '&aSekmingai issiregistravote!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&aJusu vartotojas nera patvirtintas, patikrinkite el.pasta.' usage_unreg: '&ePanaikinti registracija: "/unregister slaptazodis"' pwd_changed: '&aSlaptazodis pakeistas' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 8ebff6db5..3603a7f34 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Een herstelcode voor je wachtwoord is naar je mailbox gestu # TODO: Missing tags %count recovery_code_incorrect: 'De herstelcode is niet correct! Gebruik "/email recovery [email]" om een nieuwe te krijgen' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: 'Je account is nog niet geactiveerd, controleer je mailbox!' usage_unreg: '&cGebruik: /unregister password' pwd_changed: '&cWachtwoord succesvol aangepast!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 4600c6585..0b29216a2 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -48,6 +48,7 @@ recovery_code_sent: 'Kod odzyskiwania hasla zostal wyslany na adres email przypi # TODO: Missing tags %count recovery_code_incorrect: 'Kod odzyskiwania hasla jest bledny! Uzyj /email recovery [email] aby wygenerowac nowy.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fTwoje konto nie zostalo aktywowane! Sprawdz maila.' usage_unreg: '&cUzycie: /unregister haslo' pwd_changed: '&fHaslo zostalo zmienione!' @@ -89,6 +90,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 change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cEmail zostal wyslany, musisz poczekac %time przed wyslaniem nastepnego.' # Captcha diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index f4af6e811..e79904d63 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -47,6 +47,7 @@ two_factor_create: '&2O seu código secreto é o %code. Você pode verificá-lo # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' 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!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index 6df8a7c18..9a82c4d18 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Un cod de recuperare a parolei a fost trimis catre email-ul # TODO: Missing tags %count recovery_code_incorrect: 'Codul de recuperare nu este corect! Foloseste /email recovery [email] pentru a genera unul nou.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cContul tau nu este activat, te rugam verifica-ti email-ul!' usage_unreg: '&cFoloseste comanda: /unregister ' pwd_changed: '&2Parola a fost inregistrata cu succes!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 17c2170cc..22ac61870 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Код восстановления для сброса п # TODO: Missing tags %count recovery_code_incorrect: 'Код восстановления неверный! Введите /email recovery <Ваш Email>, чтобы отправить новый код' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!' usage_unreg: '&cИспользование: &e/unregister <Пароль>' pwd_changed: '&2Пароль изменен!' @@ -88,6 +89,7 @@ email_send_failure: 'Письмо не може быть отправлено. show_no_email: '&2В данный момент к вашему аккаунте не привязана электронная почта.' add_email: '&cДобавьте свой email: &e/email add <Ваш Email> <Ваш Email>' recovery_email: '&cЗабыли пароль? Используйте &e/email recovery <Ваш Email>' +# TODO change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cЭлектронное письмо было отправлено недавно. Пожалуйста, подождите %time прежде чем отправить новое письмо.' # Каптча diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index 901741e27..c0113afb5 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -51,6 +51,7 @@ unregistered: '&cUcet bol vymazany!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fUcet nie je aktivny. Prezri si svoj e-mail!' usage_unreg: '&cPríkaz: /unregister heslo' pwd_changed: '&cHeslo zmenené!' @@ -92,6 +93,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 9bb62f1f0..b8195283c 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Sifre sifirlama kodu eposta adresinize gonderildi.' # TODO: Missing tags %count recovery_code_incorrect: 'Kod dogru degil! Kullanim "/email recovery [eposta]" ile yeni bir kod olustur' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!' usage_unreg: '&cKullanim: /unregister ' pwd_changed: '&2Sifre basariyla degistirildi!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' email_cooldown_error: '&cKisa bir sure once eposta gonderildi. Yeni bir eposta almak icin %time beklemelisin.' # Captcha diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index dc63d3167..c2aee2a41 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -46,6 +46,7 @@ two_factor_create: '&2Ваш секретний код — %code %nl%&2Може # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cВаш акаунт ще не активовано. Будь ласка, провірте свою електронну пошту!' usage_unreg: '&cСинтаксис: /unregister <пароль>' pwd_changed: '&2Пароль успішно змінено!' @@ -87,6 +88,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index b51f4ced5..fb5a88726 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Một mã khôi phục mật khẩu đã được gửi đ # TODO: Missing tags %count recovery_code_incorrect: 'Mã khôi phục không đúng! Dùng lệnh /email recovery [email] để tạo một mã mới' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cTài khoản của bạn chưa được kích hoạt, vui lòng kiểm tra email!' usage_unreg: '&cSử dụng: /unregister ' pwd_changed: '&2Thay đổi mật khẩu thành công!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 0c0e8d0ab..6fa481bbf 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -48,6 +48,7 @@ recovery_code_sent: '一个用于重置您的密码的验证码已发到您的 # TODO: Missing tags %count recovery_code_incorrect: '验证码不正确! 使用 /email recovery [email] 以生成新的验证码' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' usage_unreg: '&8[&6玩家系统&8] &c正确用法:“/unregister <密码>”' pwd_changed: '&8[&6玩家系统&8] &c密码已成功修改!' @@ -89,6 +90,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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 7a3335a2e..684e1cc02 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -51,6 +51,7 @@ two_factor_create: '&8[&6用戶系統 - 兩步驗證碼&8] &b你的登入金鑰 # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&8[&6用戶系統&8] &f你的帳戶還沒有經過電郵驗證 !' usage_unreg: '&8[&6用戶系統&8] &f用法: 《 /unregister <密碼> 》' pwd_changed: '&8[&6用戶系統&8] &c你成功更換了你的密碼 !' @@ -92,6 +93,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index 3eeee554a..c443c38d7 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -47,6 +47,7 @@ recovery_code_sent: '已將重設密碼的恢復代碼發送到您的電子郵 # TODO: Missing tags %count recovery_code_incorrect: '恢復代碼錯誤!使用指令: "/email recovery [電郵地址]" 生成新的一個恢復代碼。' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&c你的帳戶未激活,請確認電郵!' usage_unreg: '&c使用方法: "/unregister <你的密碼>"' pwd_changed: '&2密碼已更變!' @@ -88,6 +89,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index e1129db75..6babd62c4 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -51,6 +51,7 @@ two_factor_create: '&b【AuthMe - 兩步驗證碼】&b你的登入金鑰為&9「 # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&b【AuthMe】&6你的帳號還沒有經過驗證! 檢查看看你的電子信箱 (Email) 吧!' usage_unreg: '&b【AuthMe】&6用法: &c"/unregister <密碼>"' pwd_changed: '&b【AuthMe】&6密碼變更成功!' @@ -92,6 +93,7 @@ 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 change_password_expired: 'You cannot change your password using this command anymore.' # TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' # Captcha diff --git a/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java new file mode 100644 index 000000000..194331687 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java @@ -0,0 +1,102 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.PasswordRecoveryService; +import fr.xephi.authme.service.ValidationService; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests for {@link SetPasswordCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class SetPasswordCommandTest { + + @InjectMocks + private SetPasswordCommand command; + + @Mock + private DataSource dataSource; + + @Mock + private CommonService commonService; + + @Mock + private PasswordRecoveryService recoveryService; + + @Mock + private PasswordSecurity passwordSecurity; + + @Mock + private ValidationService validationService; + + @Test + public void shouldChangePassword() { + // given + Player player = mock(Player.class); + String name = "Jerry"; + given(player.getName()).willReturn(name); + given(recoveryService.canChangePassword(player)).willReturn(true); + HashedPassword hashedPassword = passwordSecurity.computeHash("abc123", name); + given(passwordSecurity.computeHash("abc123", name)).willReturn(hashedPassword); + given(validationService.validatePassword("abc123", name)) + .willReturn(new ValidationService.ValidationResult()); + + // when + command.runCommand(player, Collections.singletonList("abc123")); + + // then + verify(validationService).validatePassword("abc123", name); + verify(dataSource).updatePassword(name, hashedPassword); + verify(commonService).send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + } + + @Test + public void shouldRejectInvalidPassword() { + // given + Player player = mock(Player.class); + String name = "Morgan"; + given(player.getName()).willReturn(name); + String password = "newPW"; + given(validationService.validatePassword(password, name)) + .willReturn(new ValidationService.ValidationResult(MessageKey.INVALID_PASSWORD_LENGTH)); + given(recoveryService.canChangePassword(player)).willReturn(true); + + // when + command.executeCommand(player, Collections.singletonList(password)); + + // then + verify(validationService).validatePassword(password, name); + verify(commonService).send(player, MessageKey.INVALID_PASSWORD_LENGTH, new String[0]); + } + + @Test + public void shouldDoNothingCantChangePass() { + // given + Player player = mock(Player.class); + String name = "Carol"; + given(player.getName()).willReturn(name); + + // when + command.runCommand(player, Collections.singletonList("abc123")); + + // then + verifyZeroInteractions(validationService); + verifyZeroInteractions(dataSource); + } +} From cfbb3f9a7f798326028585c73be566c1306d07e3 Mon Sep 17 00:00:00 2001 From: Gnat008 Date: Tue, 14 Mar 2017 18:46:39 -0400 Subject: [PATCH 091/125] Remove unnecessary stubbing in SetPasswordCommandTest --- .../authme/command/executable/email/SetPasswordCommandTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java index 194331687..dc5757d15 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/SetPasswordCommandTest.java @@ -89,8 +89,6 @@ public class SetPasswordCommandTest { public void shouldDoNothingCantChangePass() { // given Player player = mock(Player.class); - String name = "Carol"; - given(player.getName()).willReturn(name); // when command.runCommand(player, Collections.singletonList("abc123")); From 457c07b53f57e6e65c666bdb02ba08754da1c859 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 15 Mar 2017 08:24:40 +0100 Subject: [PATCH 092/125] 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 378bd1985..1e4697bf3 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 88724a847..dc5340ae6 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 093/125] 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 21b9bcb50..58781c7ee 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 094/125] 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 63922334d..65f3fe161 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 095/125] 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 58781c7ee..1fbc89964 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!"); } From 731d085ccd14e1175a052f7eea658e65364d92c7 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 17 Mar 2017 18:49:30 +0100 Subject: [PATCH 096/125] #1128 Rename to camel case (PR #235) * rename classes according to cammel case and make code reflect these updates * rename according to cammel case * rename to camel case more accuratley * rename to camel case try 3; fix Ipb4 java doc * retry rename camel case * rename to camel case --- src/main/java/fr/xephi/authme/AuthMe.java | 4 +- .../fr/xephi/authme/datasource/MySQL.java | 16 +++--- .../xephi/authme/security/HashAlgorithm.java | 52 +++++++++---------- .../xephi/authme/security/crypts/BCRYPT.java | 4 +- .../authme/security/crypts/BCRYPT2Y.java | 2 +- .../authme/security/crypts/CRAZYCRYPT1.java | 2 +- .../authme/security/crypts/DOUBLEMD5.java | 2 +- .../fr/xephi/authme/security/crypts/IPB3.java | 2 +- .../fr/xephi/authme/security/crypts/IPB4.java | 6 +-- .../xephi/authme/security/crypts/JOOMLA.java | 2 +- .../fr/xephi/authme/security/crypts/MD5.java | 2 +- .../xephi/authme/security/crypts/MD5VB.java | 2 +- .../fr/xephi/authme/security/crypts/MYBB.java | 2 +- .../xephi/authme/security/crypts/PHPBB.java | 2 +- .../authme/security/crypts/PHPFUSION.java | 2 +- .../authme/security/crypts/PLAINTEXT.java | 2 +- .../authme/security/crypts/ROYALAUTH.java | 2 +- .../authme/security/crypts/SALTED2MD5.java | 4 +- .../authme/security/crypts/SALTEDSHA512.java | 2 +- .../fr/xephi/authme/security/crypts/SHA1.java | 2 +- .../xephi/authme/security/crypts/SHA256.java | 2 +- .../xephi/authme/security/crypts/SHA512.java | 2 +- .../fr/xephi/authme/security/crypts/SMF.java | 2 +- .../fr/xephi/authme/security/crypts/WBB3.java | 2 +- .../fr/xephi/authme/security/crypts/WBB4.java | 2 +- .../authme/security/crypts/WHIRLPOOL.java | 4 +- .../authme/security/crypts/WORDPRESS.java | 2 +- .../xephi/authme/security/crypts/XAUTH.java | 8 +-- .../authme/security/crypts/XFBCRYPT.java | 2 +- .../authme/service/MigrationService.java | 8 +-- .../authme/security/crypts/BCRYPT2YTest.java | 8 +-- .../authme/security/crypts/BcryptTest.java | 8 +-- .../security/crypts/CRAZYCRYPT1Test.java | 8 +-- .../authme/security/crypts/DOUBLEMD5Test.java | 8 +-- .../authme/security/crypts/IPB3Test.java | 8 +-- .../authme/security/crypts/IPB4Test.java | 8 +-- .../authme/security/crypts/JoomlaTest.java | 4 +- .../authme/security/crypts/MD5VBTest.java | 8 +-- .../authme/security/crypts/MYBBTest.java | 8 +-- .../xephi/authme/security/crypts/Md5Test.java | 4 +- .../authme/security/crypts/PHPBBTest.java | 8 +-- .../authme/security/crypts/PHPFUSIONTest.java | 8 +-- .../authme/security/crypts/ROYALAUTHTest.java | 8 +-- .../security/crypts/SALTED2MD5Test.java | 8 +-- .../security/crypts/SALTEDSHA512Test.java | 8 +-- .../xephi/authme/security/crypts/SMFTest.java | 8 +-- .../authme/security/crypts/Sha1Test.java | 4 +- .../authme/security/crypts/Sha256Test.java | 4 +- .../authme/security/crypts/Sha512Test.java | 4 +- .../authme/security/crypts/WBB3Test.java | 8 +-- .../authme/security/crypts/WBB4Test.java | 8 +-- .../authme/security/crypts/WHIRLPOOLTest.java | 8 +-- .../authme/security/crypts/WORDPRESSTest.java | 8 +-- .../authme/security/crypts/XAUTHTest.java | 8 +-- .../authme/security/crypts/XFBCRYPTTest.java | 8 +-- .../authme/service/MigrationServiceTest.java | 7 +-- 56 files changed, 168 insertions(+), 167 deletions(-) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 74a986543..a968f41fc 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -24,7 +24,7 @@ import fr.xephi.authme.listener.PlayerListener19; import fr.xephi.authme.listener.ServerListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsSystemType; -import fr.xephi.authme.security.crypts.SHA256; +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; @@ -219,7 +219,7 @@ public class AuthMe extends JavaPlugin { instantiateServices(injector); // Convert deprecated PLAINTEXT hash entries - MigrationService.changePlainTextToSha256(settings, database, new SHA256()); + MigrationService.changePlainTextToSha256(settings, database, new Sha256()); // TODO: does this still make sense? -sgdc3 // If the server is empty (fresh start) just set all the players as unlogged diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index f500ac072..a4ddbe1c7 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -7,7 +7,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.XFBCRYPT; +import fr.xephi.authme.security.crypts.XfBCrypt; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; @@ -286,7 +286,7 @@ public class MySQL implements DataSource { if (rs.next()) { Blob blob = rs.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - auth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } } } @@ -482,8 +482,8 @@ public class MySQL implements DataSource { sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)"; pst2 = con.prepareStatement(sql); pst2.setInt(1, id); - pst2.setString(2, XFBCRYPT.SCHEME_CLASS); - String serializedHash = XFBCRYPT.serializeHash(auth.getPassword().getHash()); + pst2.setString(2, XfBCrypt.SCHEME_CLASS); + String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash()); byte[] bytes = serializedHash.getBytes(); Blob blob = con.createBlob(); blob.setBytes(1, bytes); @@ -538,7 +538,7 @@ public class MySQL implements DataSource { // Insert password in the correct table sql = "UPDATE xf_user_authenticate SET data=? WHERE " + col.ID + "=?;"; PreparedStatement pst2 = con.prepareStatement(sql); - String serializedHash = XFBCRYPT.serializeHash(password.getHash()); + String serializedHash = XfBCrypt.serializeHash(password.getHash()); byte[] bytes = serializedHash.getBytes(); Blob blob = con.createBlob(); blob.setBytes(1, bytes); @@ -549,7 +549,7 @@ public class MySQL implements DataSource { // ... sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;"; pst2 = con.prepareStatement(sql); - pst2.setString(1, XFBCRYPT.SCHEME_CLASS); + pst2.setString(1, XfBCrypt.SCHEME_CLASS); pst2.setInt(2, id); pst2.executeUpdate(); pst2.close(); @@ -824,7 +824,7 @@ public class MySQL implements DataSource { if (rs2.next()) { Blob blob = rs2.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } rs2.close(); } @@ -856,7 +856,7 @@ public class MySQL implements DataSource { if (rs2.next()) { Blob blob = rs2.getBlob("data"); byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes))); } rs2.close(); } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 732582e03..f12da678d 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -7,36 +7,36 @@ import fr.xephi.authme.security.crypts.EncryptionMethod; */ public enum HashAlgorithm { - BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), - BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), - CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), - DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class), - IPB3(fr.xephi.authme.security.crypts.IPB3.class), - IPB4(fr.xephi.authme.security.crypts.IPB4.class), - JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), - MD5(fr.xephi.authme.security.crypts.MD5.class), - MD5VB(fr.xephi.authme.security.crypts.MD5VB.class), - MYBB(fr.xephi.authme.security.crypts.MYBB.class), + BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class), + BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class), + CRAZYCRYPT1(fr.xephi.authme.security.crypts.CrazyCrypt1.class), + DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class), + IPB3(fr.xephi.authme.security.crypts.Ipb3.class), + IPB4(fr.xephi.authme.security.crypts.Ipb4.class), + JOOMLA(fr.xephi.authme.security.crypts.Joomla.class), + MD5(fr.xephi.authme.security.crypts.Md5.class), + MD5VB(fr.xephi.authme.security.crypts.Md5vB.class), + MYBB(fr.xephi.authme.security.crypts.MyBB.class), PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class), - PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), - PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), + PHPBB(fr.xephi.authme.security.crypts.PhpBB.class), + PHPFUSION(fr.xephi.authme.security.crypts.PhpFusion.class), @Deprecated - PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), - ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class), - SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), - SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), - SHA1(fr.xephi.authme.security.crypts.SHA1.class), - SHA256(fr.xephi.authme.security.crypts.SHA256.class), - SHA512(fr.xephi.authme.security.crypts.SHA512.class), - SMF(fr.xephi.authme.security.crypts.SMF.class), + PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class), + ROYALAUTH(fr.xephi.authme.security.crypts.RoyalAuth.class), + SALTED2MD5(fr.xephi.authme.security.crypts.Salted2Md5.class), + SALTEDSHA512(fr.xephi.authme.security.crypts.SaltedSha512.class), + SHA1(fr.xephi.authme.security.crypts.Sha1.class), + SHA256(fr.xephi.authme.security.crypts.Sha256.class), + SHA512(fr.xephi.authme.security.crypts.Sha512.class), + SMF(fr.xephi.authme.security.crypts.Smf.class), TWO_FACTOR(fr.xephi.authme.security.crypts.TwoFactor.class), - WBB3(fr.xephi.authme.security.crypts.WBB3.class), - WBB4(fr.xephi.authme.security.crypts.WBB4.class), - WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), - WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), - XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), - XFBCRYPT(fr.xephi.authme.security.crypts.XFBCRYPT.class), + WBB3(fr.xephi.authme.security.crypts.Wbb3.class), + WBB4(fr.xephi.authme.security.crypts.Wbb4.class), + WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class), + WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class), + XAUTH(fr.xephi.authme.security.crypts.XAuth.class), + XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class), CUSTOM(null); private final Class clazz; diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index aae6b9109..02e12d459 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -14,12 +14,12 @@ import javax.inject.Inject; @Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 @HasSalt(value = SaltType.TEXT) // length depends on the bcryptLog2Rounds setting -public class BCRYPT implements EncryptionMethod { +public class BCrypt implements EncryptionMethod { private final int bCryptLog2Rounds; @Inject - public BCRYPT(Settings settings) { + public BCrypt(Settings settings) { bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index 49bd45f8a..cf4807abc 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -4,7 +4,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class BCRYPT2Y extends HexSaltedMethod { +public class BCrypt2y extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index 595842314..6130c6a12 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.MessageDigestAlgorithm; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -public class CRAZYCRYPT1 extends UsernameSaltMethod { +public class CrazyCrypt1 extends UsernameSaltMethod { private static final char[] CRYPTCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index b7823a2b4..c28e0440a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import static fr.xephi.authme.security.HashUtils.md5; -public class DOUBLEMD5 extends UnsaltedMethod { +public class DoubleMd5 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index a4e62461c..15dcd189b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 5) -public class IPB3 extends SeparateSaltMethod { +public class Ipb3 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java b/src/main/java/fr/xephi/authme/security/crypts/IPB4.java index 40ea75166..762897955 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB4.java @@ -11,15 +11,15 @@ import fr.xephi.authme.util.StringUtils; /** - * Implementation for IPB4 (Invision Power Board 4). + * Implementation for Ipb4 (Invision Power Board 4). *

    * The hash uses standard BCrypt with 13 as log2 number of rounds. Additionally, - * IPB4 requires that the salt be stored additionally in the column "members_pass_hash" + * Ipb4 requires that the salt be stored additionally in the column "members_pass_hash" * (even though BCrypt hashes already have the salt in the result). */ @Recommendation(Usage.DOES_NOT_WORK) @HasSalt(value = SaltType.TEXT, length = 22) -public class IPB4 implements EncryptionMethod { +public class Ipb4 implements EncryptionMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index ca72674b3..462f5cb28 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -5,7 +5,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.ACCEPTABLE) -public class JOOMLA extends HexSaltedMethod { +public class Joomla extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index 50bb7d97e..c2a2ba04a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class MD5 extends UnsaltedMethod { +public class Md5 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index f9c21ae7e..c244ec49d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import static fr.xephi.authme.security.HashUtils.md5; -public class MD5VB extends HexSaltedMethod { +public class Md5vB extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index b25f47695..3f0a477ab 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 8) -public class MYBB extends SeparateSaltMethod { +public class MyBB extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index 074143fd6..e5f7e54ca 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -9,7 +9,7 @@ import java.security.MessageDigest; /** * @author stefano */ -public class PHPBB extends HexSaltedMethod { +public class PhpBB extends HexSaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 905798ec3..5a49ed4ce 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException; @Recommendation(Usage.DO_NOT_USE) @AsciiRestricted -public class PHPFUSION extends SeparateSaltMethod { +public class PhpFusion extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index a294ca917..add333d69 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -6,7 +6,7 @@ package fr.xephi.authme.security.crypts; * @deprecated Using this is no longer supported. AuthMe will migrate to SHA256 on startup. */ @Deprecated -public class PLAINTEXT extends UnsaltedMethod { +public class PlainText extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index db089e360..989ef8383 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class ROYALAUTH extends UnsaltedMethod { +public class RoyalAuth extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index b2f22ab9b..6d708b3ce 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -14,12 +14,12 @@ import static fr.xephi.authme.security.HashUtils.md5; @Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) @HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting -public class SALTED2MD5 extends SeparateSaltMethod { +public class Salted2Md5 extends SeparateSaltMethod { private final int saltLength; @Inject - public SALTED2MD5(Settings settings) { + public Salted2Md5(Settings settings) { saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index f0f293439..b5660d658 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class SALTEDSHA512 extends SeparateSaltMethod { +public class SaltedSha512 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index 080910ec7..e3d078e7e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SHA1 extends UnsaltedMethod { +public class Sha1 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index ee55d4512..1b77a2e44 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.HashUtils.sha256; @Recommendation(Usage.RECOMMENDED) -public class SHA256 extends HexSaltedMethod { +public class Sha256 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 81f1e0263..12e51a315 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SHA512 extends UnsaltedMethod { +public class Sha512 extends UnsaltedMethod { @Override public String computeHash(String password) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index 175efc3fa..46114d2e9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; -public class SMF extends UsernameSaltMethod { +public class Smf extends UsernameSaltMethod { @Override public HashedPassword computeHash(String password, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index 546c2dc87..0a042b489 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.sha1; @Recommendation(Usage.ACCEPTABLE) @HasSalt(value = SaltType.TEXT, length = 40) -public class WBB3 extends SeparateSaltMethod { +public class Wbb3 extends SeparateSaltMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index a81503374..d1d4953d1 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage; import static fr.xephi.authme.security.crypts.BCryptService.hashpw; @Recommendation(Usage.RECOMMENDED) -public class WBB4 extends HexSaltedMethod { +public class Wbb4 extends HexSaltedMethod { @Override public String computeHash(String password, String salt, String name) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 72d8e8fb6..84efae8e8 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -61,7 +61,7 @@ package fr.xephi.authme.security.crypts; import java.util.Arrays; -public class WHIRLPOOL extends UnsaltedMethod { +public class Whirlpool extends UnsaltedMethod { /** * The message digest size (in bits) @@ -158,7 +158,7 @@ public class WHIRLPOOL extends UnsaltedMethod { protected final long[] block = new long[8]; protected final long[] state = new long[8]; - public WHIRLPOOL() { + public Whirlpool() { } protected static String display(byte[] array) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index f331d1fc6..768b92c5d 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -16,7 +16,7 @@ import java.util.Arrays; @HasSalt(value = SaltType.TEXT, length = 9) // Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally // and isn't exposed to the outside, so we treat it as an unsalted implementation -public class WORDPRESS extends UnsaltedMethod { +public class Wordpress extends UnsaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private final SecureRandom randomGen = new SecureRandom(); diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index f2ebf1976..9f921b6ae 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -4,15 +4,15 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class XAUTH extends HexSaltedMethod { +public class XAuth extends HexSaltedMethod { private static String getWhirlpool(String message) { - WHIRLPOOL w = new WHIRLPOOL(); - byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; + Whirlpool w = new Whirlpool(); + byte[] digest = new byte[Whirlpool.DIGESTBYTES]; w.NESSIEinit(); w.NESSIEadd(message); w.NESSIEfinalize(digest); - return WHIRLPOOL.display(digest); + return Whirlpool.display(digest); } @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java index a20ee65aa..bf5545290 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java @@ -7,7 +7,7 @@ import fr.xephi.authme.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class XFBCRYPT implements EncryptionMethod { +public class XfBCrypt implements EncryptionMethod { public static final String SCHEME_CLASS = "XenForo_Authentication_Core12"; private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); diff --git a/src/main/java/fr/xephi/authme/service/MigrationService.java b/src/main/java/fr/xephi/authme/service/MigrationService.java index 043de8988..338ef51a1 100644 --- a/src/main/java/fr/xephi/authme/service/MigrationService.java +++ b/src/main/java/fr/xephi/authme/service/MigrationService.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -20,14 +20,14 @@ public final class MigrationService { } /** - * Hash all passwords to SHA256 and updated the setting if the password hash is set to the deprecated PLAINTEXT. + * Hash all passwords to Sha256 and updated the setting if the password hash is set to the deprecated PLAINTEXT. * * @param settings The settings instance * @param dataSource The data source - * @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation + * @param authmeSha256 Instance to the AuthMe Sha256 encryption method implementation */ public static void changePlainTextToSha256(Settings settings, DataSource dataSource, - SHA256 authmeSha256) { + Sha256 authmeSha256) { if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) { ConsoleLogger.warning("Your HashAlgorithm has been detected as plaintext and is now deprecated;" + " it will be changed and hashed now to the AuthMe default hashing method"); diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java index ac34dea02..83308c9fc 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link BCRYPT2Y}. + * Test for {@link BCrypt2y}. */ -public class BCRYPT2YTest extends AbstractEncryptionMethodTest { +public class BCrypt2yTest extends AbstractEncryptionMethodTest { - public BCRYPT2YTest() { - super(new BCRYPT2Y(), + public BCrypt2yTest() { + super(new BCrypt2y(), "$2y$10$da641e404b982edf1c7c0uTU9BcKzfA2vWKV05q6r.dCvm/93wqVK", // password "$2y$10$e52c48a76f5b86f5da899uiK/HYocyPsfQXESNbP278rIz08LKEP2", // PassWord1 "$2y$10$be6f11548dc5fb4088410ONdC0dXnJ04y1RHcJh5fVF3XK5d.qgqK", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java index 1fe9ed086..c2d9d6f5a 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -7,12 +7,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Test for {@link BCRYPT}. + * Test for {@link BCrypt}. */ -public class BcryptTest extends AbstractEncryptionMethodTest { +public class BCryptTest extends AbstractEncryptionMethodTest { - public BcryptTest() { - super(new BCRYPT(mockSettings()), + public BCryptTest() { + super(new BCrypt(mockSettings()), "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password "$2a$10$LOhUxhEcS0vgDPv/jkXvCurNb7LjP9xUlEolJGk.Uhgikqc6FtIOi", // PassWord1 "$2a$10$j9da7SGiaakWhzIms9BtwemLUeIhSEphGUQ3XSlvYgpYsGnGCKRBa", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java index fa27de3b1..f98ece5ab 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link CRAZYCRYPT1}. + * Test for {@link CrazyCrypt1}. */ -public class CRAZYCRYPT1Test extends AbstractEncryptionMethodTest { +public class CrazyCrypt1Test extends AbstractEncryptionMethodTest { - public CRAZYCRYPT1Test() { - super(new CRAZYCRYPT1(), + public CrazyCrypt1Test() { + super(new CrazyCrypt1(), "d5c76eb36417d4e97ec62609619e40a9e549a2598d0dab5a7194fd997a9305af78de2b93f958e150d19dd1e7f821043379ddf5f9c7f352bf27df91ae4913f3e8", // password "49c63f827c88196871e344e589bd46cc4fa6db3c27801bbad5374c0d216381977627c1d76f2114667d5dd117e046f7493eb06e4f461f4f848aa08f6f40a3e934", // PassWord1 "6fefb0233bab6e6efb9c16f82cb0d8f569488905e2dae0e7c9dde700e7363da67213d37c44bc15f4a05854c9c21e5688389d416413c7309398aa96cb1f341d08", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java index 530399516..cc12df9e5 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link DOUBLEMD5}. + * Test for {@link DoubleMd5}. */ -public class DOUBLEMD5Test extends AbstractEncryptionMethodTest { +public class DoubleMd5Test extends AbstractEncryptionMethodTest { - public DOUBLEMD5Test() { - super(new DOUBLEMD5(), + public DoubleMd5Test() { + super(new DoubleMd5(), "696d29e0940a4957748fe3fc9efd22a3", // password "c77aa2024d9fb7233a2872452d601aba", // PassWord1 "fbd5790af706ec19f8a7ef161878758b", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java index cf42567eb..984f3d217 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link IPB3}. + * Test for {@link Ipb3}. */ -public class IPB3Test extends AbstractEncryptionMethodTest { +public class Ipb3Test extends AbstractEncryptionMethodTest { - public IPB3Test() { - super(new IPB3(), + public Ipb3Test() { + super(new Ipb3(), new HashedPassword("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password new HashedPassword("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 new HashedPassword("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java index 28c76a09f..5f71c23d7 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link IPB4}. + * Test for {@link Ipb4}. */ -public class IPB4Test extends AbstractEncryptionMethodTest { +public class Ipb4Test extends AbstractEncryptionMethodTest { - public IPB4Test() { - super(new IPB4(), + public Ipb4Test() { + super(new Ipb4(), new HashedPassword("$2a$13$leEvXu77OIwPwNvtZIJvaeAx8EItGHuR3nIlq8416g0gXeJaQdrr2", "leEvXu77OIwPwNvtZIJval"), //password new HashedPassword("$2a$13$xyTTP9zhQQtRRKIJPv5AuuOGJ6Ni9FLbDhcuIAcPjt3XzCxIWe3Uu", "xyTTP9zhQQtRRKIJPv5Au3"), //PassWord1 new HashedPassword("$2a$13$rGBrqErm9DZyzbxIGHlgf.xfA15/4d5Ay/TK.3y9lG3AljcoG9Lsi", "rGBrqErm9DZyzbxIGHlgfN"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java index f46d51d83..d2673b34c 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/JoomlaTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link JOOMLA}. + * Test for {@link Joomla}. */ public class JoomlaTest extends AbstractEncryptionMethodTest { public JoomlaTest() { - super(new JOOMLA(), + super(new Joomla(), "b18c99813cd96df3a706652f47177490:377c4aaf92c5ed57711306909e6065ca", // password "c5af71da91a8841d95937ba24a5b7fdb:07068e5850930b794526a614438cafc7", // PassWord1 "f5fccd5166af7080833d7c7a6a531295:7cb6eeabcfac67ffe1341ec43375a9e6", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java index acb823e66..eac20fcd1 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MD5VB}. + * Test for {@link Md5vB}. */ -public class MD5VBTest extends AbstractEncryptionMethodTest { +public class Md5vBTest extends AbstractEncryptionMethodTest { - public MD5VBTest() { - super(new MD5VB(), + public Md5vBTest() { + super(new Md5vB(), "$MD5vb$bd9832fffa287321$5006d371fcb813f2347987f902a024ad", // password "$MD5vb$5e492c1166b5a828$c954fa5ee561700a097826971653b57f", // PassWord1 "$MD5vb$3ec43cd46a61d70b$59687c0976f2e327b1245c8063f7008c", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java index 101f475ff..fabd9f35d 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MYBB}. + * Test for {@link MyBB}. */ -public class MYBBTest extends AbstractEncryptionMethodTest { +public class MyBBTest extends AbstractEncryptionMethodTest { - public MYBBTest() { - super(new MYBB(), + public MyBBTest() { + super(new MyBB(), new HashedPassword("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password new HashedPassword("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 new HashedPassword("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java index 0c9d67cf1..c07918371 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link MD5}. + * Test for {@link Md5}. */ public class Md5Test extends AbstractEncryptionMethodTest { public Md5Test() { - super(new MD5(), + super(new Md5(), "5f4dcc3b5aa765d61d8327deb882cf99", // password "f2126d405f46ed603ff5b2950f062c96", // PassWord1 "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java index 61a423609..f495659ec 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link PHPBB}. + * Test for {@link PhpBB}. */ -public class PHPBBTest extends AbstractEncryptionMethodTest { +public class PhpBBTest extends AbstractEncryptionMethodTest { - public PHPBBTest() { - super(new PHPBB(), + public PhpBBTest() { + super(new PhpBB(), "$H$7MaSGQb0xe3Fp/a.Q.Ewpw.UKfCv.t0", // password "$H$7ESfAVjzqajC7fJFcZKZIhyds41MuW.", // PassWord1 "$H$7G65SXRPbR69jLg.qZTjtqsw36Ciw7.", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java index 0b10c1c72..a08a6ad8c 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link PHPFUSION}. + * Test for {@link PhpFusion}. */ -public class PHPFUSIONTest extends AbstractEncryptionMethodTest { +public class PhpFusionTest extends AbstractEncryptionMethodTest { - public PHPFUSIONTest() { - super(new PHPFUSION(), + public PhpFusionTest() { + super(new PhpFusion(), new HashedPassword("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password new HashedPassword("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 new HashedPassword("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java index 0de9c2f39..7734dc03a 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link ROYALAUTH}. + * Test for {@link RoyalAuth}. */ -public class ROYALAUTHTest extends AbstractEncryptionMethodTest { +public class RoyalAuthTest extends AbstractEncryptionMethodTest { - public ROYALAUTHTest() { - super(new ROYALAUTH(), + public RoyalAuthTest() { + super(new RoyalAuth(), "5d21ef9236896bc4ac508e524e2da8a0def555dac1cdfc7259d62900d1d3f553826210c369870673ae2cf1c41abcf4f92670d76af1db044d33559324f5c2a339", // password "ecc685f4328bc54093c086ced66c5c11855e117ea22940632d5c0f55fff84d94bfdcc74e05f5d95bbdd052823a7057910748bc1c7a07af96b3e86731a4f11794", // PassWord1 "2c0b4674f7c2c266db13ae4382cbeee3083167a774f6e73793a6268a0b8b2c3c6b324a99596f4a7958e58c5311c77e25975a3b517ce17adfc4eaece821e3dd19", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java index 20ec99fe8..fc34a13fc 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java @@ -7,12 +7,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Test for {@link SALTED2MD5}. + * Test for {@link Salted2Md5}. */ -public class SALTED2MD5Test extends AbstractEncryptionMethodTest { +public class Salted2Md5Test extends AbstractEncryptionMethodTest { - public SALTED2MD5Test() { - super(new SALTED2MD5(mockSettings()), + public Salted2Md5Test() { + super(new Salted2Md5(mockSettings()), new HashedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password new HashedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 new HashedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java index 94cc78e82..98ab06153 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SALTEDSHA512}. + * Test for {@link SaltedSha512}. */ -public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { +public class SaltedSha512Test extends AbstractEncryptionMethodTest { - public SALTEDSHA512Test() { - super(new SALTEDSHA512(), + public SaltedSha512Test() { + super(new SaltedSha512(), new HashedPassword("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password new HashedPassword("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 new HashedPassword("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java b/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java index 8ba3b1973..53803334a 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SMF}. + * Test for {@link Smf}. */ -public class SMFTest extends AbstractEncryptionMethodTest { +public class SmfTest extends AbstractEncryptionMethodTest { - public SMFTest() { - super(new SMF(), + public SmfTest() { + super(new Smf(), "9b361c66977bb059d460a20d3c21fb3394772df5", // password "31a560bdd095a837945d46add1605108ba87b268", // PassWord1 "8d4b84544e0891be8c183fe9b1003cfac18c51a1", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java index 620cef270..2bd5543f3 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha1Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA1}. + * Test for {@link Sha1}. */ public class Sha1Test extends AbstractEncryptionMethodTest { public Sha1Test() { - super(new SHA1(), + super(new Sha1(), "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", // password "285d0c707f9644b75e1a87a62f25d0efb56800f0", // PassWord1 "a42ef8e61e890af80461ca5dcded25cbfcf407a4", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java index 3257fe1f5..b9f751654 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha256Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA256}. + * Test for {@link Sha256}. */ public class Sha256Test extends AbstractEncryptionMethodTest { public Sha256Test() { - super(new SHA256(), + super(new Sha256(), "$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10", // password "$SHA$3c72a18a29b08d40$8e50a7a4f69a80f4893dc921eac84bd74b3f9ebfa22908302c9965eac3aa45e5", // PassWord1 "$SHA$584cea1cfab90030$adc006330e73d81e463fe02a4fe9b17bdbbcc05955bff72fb27cf2089f0b3859", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java b/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java index 17ba989c6..2871cd3a2 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Sha512Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link SHA512}. + * Test for {@link Sha512}. */ public class Sha512Test extends AbstractEncryptionMethodTest { public Sha512Test() { - super(new SHA512(), + super(new Sha512(), "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86", // password "ae9942149995a8171391625b36da134d5e288c721650d7c8d2d464fb49a49f3f551e4916ab1e097d9dd1201b01d69b1dccdefa3d2524a66092fb61b3df6e7e71", // PassWord1 "8c4f3df78db191142d819a72c16058b9e1ea41ae9b1649e1184eb89e30344c51c9c71039c483cf2f1b76b51480d8459d7eb3cfbaa24b07f2041d1551af4ead75", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java index d838c14fc..7443253fe 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WBB3}. + * Test for {@link Wbb3}. */ -public class WBB3Test extends AbstractEncryptionMethodTest { +public class Wbb3Test extends AbstractEncryptionMethodTest { - public WBB3Test() { - super(new WBB3(), + public Wbb3Test() { + super(new Wbb3(), new HashedPassword("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password new HashedPassword("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 new HashedPassword("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java index 5b714cab3..6c5459f60 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WBB4}. + * Test for {@link Wbb4}. */ -public class WBB4Test extends AbstractEncryptionMethodTest { +public class Wbb4Test extends AbstractEncryptionMethodTest { - public WBB4Test() { - super(new WBB4(), + public Wbb4Test() { + super(new Wbb4(), "$2a$08$7DGr.wROqEPe0Z3XJS7n5.k.QWehovLHbpI.UkdfRb4ns268WsR6C", // password "$2a$08$yWWVUA4PB4mqW.0wyIvV3OdoH492HuLk5L3iaqUrpRK2.2zn08d/K", // PassWord1 "$2a$08$EHXUFt7bTT9Fnsu22KWvF.QDssiosV8YzH8CyWqulB/ckOA7qioJG", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java b/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java index 1fbc94fd8..76cef4b05 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WHIRLPOOL}. + * Test for {@link Whirlpool}. */ -public class WHIRLPOOLTest extends AbstractEncryptionMethodTest { +public class WhirlpoolTest extends AbstractEncryptionMethodTest { - public WHIRLPOOLTest() { - super(new WHIRLPOOL(), + public WhirlpoolTest() { + super(new Whirlpool(), "74DFC2B27ACFA364DA55F93A5CAEE29CCAD3557247EDA238831B3E9BD931B01D77FE994E4F12B9D4CFA92A124461D2065197D8CF7F33FC88566DA2DB2A4D6EAE", // password "819B4CBD26508E39EA76BFE102DCF2ACC87A446747CAB0BD88522B0822A724583E81B6A4BD2CE255DB694E530B659F47D434EEB50344A02F50B64414C9671583", // PassWord1 "71ECB0E5AEAB006F5336348076AA6A8E46075AEC9E010C7055BA1334B57746F2A9D8A8799BDD9B7EB4AB7544A59D25F469C8BCA2067508ACBA62A929260A1E17", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java index 49a16d65b..0c444c2f8 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link WORDPRESS}. + * Test for {@link Wordpress}. */ -public class WORDPRESSTest extends AbstractEncryptionMethodTest { +public class WordpressTest extends AbstractEncryptionMethodTest { - public WORDPRESSTest() { - super(new WORDPRESS(), + public WordpressTest() { + super(new Wordpress(), "$P$B9wyjxuU4yrfjnnHNGSzH9ti9CC0Os1", // password "$P$BjzPjjzPjjkRzvGGRTyYu0sNqcz6Ci0", // PassWord1 "$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java index 4fcd8039b..877a81ee1 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link XAUTH}. + * Test for {@link XAuth}. */ -public class XAUTHTest extends AbstractEncryptionMethodTest { +public class XAuthTest extends AbstractEncryptionMethodTest { - public XAUTHTest() { - super(new XAUTH(), + public XAuthTest() { + super(new XAuth(), "e54d4916577410d26d2f6e9362445463dab9ffdff9a67ed3b74d3f2312bc8fab84f653fcb88ad8338793ef8a6d0a1162105e46ec24f0dcb52355c634e3e6439f45444b09c715", // password "d54489a4fd4732ee03d56810ab92944096e3d49335266adeecfbc12567abb3ff744761b33a1fcc4d04739e377775c788e4baace3caf35c7b9176b82b1fe3472e4cbdc5a43214", // PassWord1 "ce6404c1092fb5abf0a72f9c4327bfe8f4cdc4b8dc90ee6ca35c42b8ae9481b89c2559bb60b99ff2b57a102cfced40b8e2f5ef481400c9e6f79445017fc763b1cc27f4c2df36", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java index 4edafbdd0..af1f4589b 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link XFBCRYPT}. + * Test for {@link XfBCrypt}. */ -public class XFBCRYPTTest extends AbstractEncryptionMethodTest { +public class XfBCryptTest extends AbstractEncryptionMethodTest { - public XFBCRYPTTest() { - super(new XFBCRYPT(), + public XfBCryptTest() { + super(new XfBCrypt(), "$2a$10$UtuON/ZG.x8EWG/zQbryB.BHfQVrfxk3H7qykzP.UJQ8YiLjZyfqq", // password "$2a$10$Q.ocUo.YtHTdI4nu3pcpKun6BILcmWHm541ANULucmuU/ps1QKY4K", // PassWord1 "$2a$10$yHjm02.K4HP5iFU1F..yLeTeo7PWZVbKAr/QGex5jU4.J3mdq/uuO", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java index fa82b97ae..11a0f253f 100644 --- a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.junit.BeforeClass; @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import static fr.xephi.authme.AuthMeMatchers.equalToHash; /** * Test for {@link MigrationService}. @@ -42,7 +43,7 @@ public class MigrationServiceTest { private DataSource dataSource; @Mock - private SHA256 sha256; + private Sha256 sha256; @BeforeClass public static void setUpLogger() { @@ -122,7 +123,7 @@ public class MigrationServiceTest { .build(); } - private static void setSha256MockToUppercase(SHA256 sha256) { + private static void setSha256MockToUppercase(Sha256 sha256) { given(sha256.computeHash(anyString(), anyString())).willAnswer(new Answer() { @Override public HashedPassword answer(InvocationOnMock invocation) { From 6bd0b7c4e0b4a07b5375c6f9a994fd6d4ce4a2f0 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 17 Mar 2017 19:17:38 +0100 Subject: [PATCH 097/125] #1128 Rename files to match new case of Java classes --- src/main/java/fr/xephi/authme/security/HashAlgorithm.java | 2 +- .../authme/security/crypts/{BCRYPT.java => BCrypt.java} | 0 .../security/crypts/{BCRYPT2Y.java => BCrypt2y.java} | 0 .../crypts/{CRAZYCRYPT1.java => CrazyCrypt1.java} | 0 .../security/crypts/{DOUBLEMD5.java => DoubleMd5.java} | 0 .../xephi/authme/security/crypts/{IPB3.java => Ipb3.java} | 0 .../xephi/authme/security/crypts/{IPB4.java => Ipb4.java} | 0 .../authme/security/crypts/{JOOMLA.java => Joomla.java} | 0 .../xephi/authme/security/crypts/{MD5.java => Md5.java} | 0 .../authme/security/crypts/{MD5VB.java => Md5vB.java} | 0 .../xephi/authme/security/crypts/{MYBB.java => MyBB.java} | 0 .../authme/security/crypts/{PHPBB.java => PhpBB.java} | 0 .../security/crypts/{PHPFUSION.java => PhpFusion.java} | 0 .../security/crypts/{PLAINTEXT.java => PlainText.java} | 0 .../security/crypts/{ROYALAUTH.java => RoyalAuth.java} | 0 .../security/crypts/{SALTED2MD5.java => Salted2Md5.java} | 0 .../crypts/{SALTEDSHA512.java => SaltedSha512.java} | 0 .../xephi/authme/security/crypts/{SHA1.java => Sha1.java} | 0 .../authme/security/crypts/{SHA256.java => Sha256.java} | 0 .../authme/security/crypts/{SHA512.java => Sha512.java} | 0 .../xephi/authme/security/crypts/{SMF.java => Smf.java} | 0 .../xephi/authme/security/crypts/{WBB3.java => Wbb3.java} | 0 .../xephi/authme/security/crypts/{WBB4.java => Wbb4.java} | 0 .../security/crypts/{WHIRLPOOL.java => Whirlpool.java} | 0 .../security/crypts/{WORDPRESS.java => Wordpress.java} | 0 .../authme/security/crypts/{XAUTH.java => Xauth.java} | 2 +- .../security/crypts/{XFBCRYPT.java => XfBCrypt.java} | 0 .../fr/xephi/authme/security/PasswordSecurityTest.java | 4 ++-- .../crypts/{BCRYPT2YTest.java => BCrypt2yTest.java} | 0 .../security/crypts/{BcryptTest.java => BCryptTest.java} | 0 .../crypts/{CRAZYCRYPT1Test.java => CrazyCrypt1Test.java} | 0 .../crypts/{DOUBLEMD5Test.java => DoubleMd5Test.java} | 0 .../security/crypts/{IPB3Test.java => Ipb3Test.java} | 0 .../security/crypts/{IPB4Test.java => Ipb4Test.java} | 0 .../security/crypts/{MD5VBTest.java => Md5vBTest.java} | 0 .../security/crypts/{MYBBTest.java => MyBBTest.java} | 0 .../security/crypts/{PHPBBTest.java => PhpBBTest.java} | 0 .../crypts/{PHPFUSIONTest.java => PhpFusionTest.java} | 0 .../crypts/{ROYALAUTHTest.java => RoyalAuthTest.java} | 0 .../crypts/{SALTED2MD5Test.java => Salted2Md5Test.java} | 0 .../{SALTEDSHA512Test.java => SaltedSha512Test.java} | 0 .../authme/security/crypts/{SMFTest.java => SmfTest.java} | 0 .../security/crypts/{WBB3Test.java => Wbb3Test.java} | 0 .../security/crypts/{WBB4Test.java => Wbb4Test.java} | 0 .../crypts/{WHIRLPOOLTest.java => WhirlpoolTest.java} | 0 .../crypts/{WORDPRESSTest.java => WordpressTest.java} | 0 .../security/crypts/{XAUTHTest.java => XauthTest.java} | 8 ++++---- .../crypts/{XFBCRYPTTest.java => XfBCryptTest.java} | 0 48 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/fr/xephi/authme/security/crypts/{BCRYPT.java => BCrypt.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{BCRYPT2Y.java => BCrypt2y.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{CRAZYCRYPT1.java => CrazyCrypt1.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{DOUBLEMD5.java => DoubleMd5.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{IPB3.java => Ipb3.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{IPB4.java => Ipb4.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{JOOMLA.java => Joomla.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{MD5.java => Md5.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{MD5VB.java => Md5vB.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{MYBB.java => MyBB.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{PHPBB.java => PhpBB.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{PHPFUSION.java => PhpFusion.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{PLAINTEXT.java => PlainText.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{ROYALAUTH.java => RoyalAuth.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SALTED2MD5.java => Salted2Md5.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SALTEDSHA512.java => SaltedSha512.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SHA1.java => Sha1.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SHA256.java => Sha256.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SHA512.java => Sha512.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{SMF.java => Smf.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{WBB3.java => Wbb3.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{WBB4.java => Wbb4.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{WHIRLPOOL.java => Whirlpool.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{WORDPRESS.java => Wordpress.java} (100%) rename src/main/java/fr/xephi/authme/security/crypts/{XAUTH.java => Xauth.java} (96%) rename src/main/java/fr/xephi/authme/security/crypts/{XFBCRYPT.java => XfBCrypt.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{BCRYPT2YTest.java => BCrypt2yTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{BcryptTest.java => BCryptTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{CRAZYCRYPT1Test.java => CrazyCrypt1Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{DOUBLEMD5Test.java => DoubleMd5Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{IPB3Test.java => Ipb3Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{IPB4Test.java => Ipb4Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{MD5VBTest.java => Md5vBTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{MYBBTest.java => MyBBTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{PHPBBTest.java => PhpBBTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{PHPFUSIONTest.java => PhpFusionTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{ROYALAUTHTest.java => RoyalAuthTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{SALTED2MD5Test.java => Salted2Md5Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{SALTEDSHA512Test.java => SaltedSha512Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{SMFTest.java => SmfTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{WBB3Test.java => Wbb3Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{WBB4Test.java => Wbb4Test.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{WHIRLPOOLTest.java => WhirlpoolTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{WORDPRESSTest.java => WordpressTest.java} (100%) rename src/test/java/fr/xephi/authme/security/crypts/{XAUTHTest.java => XauthTest.java} (84%) rename src/test/java/fr/xephi/authme/security/crypts/{XFBCRYPTTest.java => XfBCryptTest.java} (100%) diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index f12da678d..75137717c 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -35,7 +35,7 @@ public enum HashAlgorithm { WBB4(fr.xephi.authme.security.crypts.Wbb4.class), WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class), WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class), - XAUTH(fr.xephi.authme.security.crypts.XAuth.class), + XAUTH(fr.xephi.authme.security.crypts.Xauth.class), XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class), CUSTOM(null); diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java rename to src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java rename to src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/IPB3.java rename to src/main/java/fr/xephi/authme/security/crypts/Ipb3.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/IPB4.java rename to src/main/java/fr/xephi/authme/security/crypts/Ipb4.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java rename to src/main/java/fr/xephi/authme/security/crypts/Joomla.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/Md5.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/MD5.java rename to src/main/java/fr/xephi/authme/security/crypts/Md5.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/MD5VB.java rename to src/main/java/fr/xephi/authme/security/crypts/Md5vB.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/MYBB.java rename to src/main/java/fr/xephi/authme/security/crypts/MyBB.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/PHPBB.java rename to src/main/java/fr/xephi/authme/security/crypts/PhpBB.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java rename to src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java rename to src/main/java/fr/xephi/authme/security/crypts/PlainText.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java rename to src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java rename to src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java rename to src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SHA1.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha1.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SHA256.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha256.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SHA512.java rename to src/main/java/fr/xephi/authme/security/crypts/Sha512.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/Smf.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/SMF.java rename to src/main/java/fr/xephi/authme/security/crypts/Smf.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/WBB3.java rename to src/main/java/fr/xephi/authme/security/crypts/Wbb3.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/WBB4.java rename to src/main/java/fr/xephi/authme/security/crypts/Wbb4.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java rename to src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java rename to src/main/java/fr/xephi/authme/security/crypts/Wordpress.java diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/Xauth.java similarity index 96% rename from src/main/java/fr/xephi/authme/security/crypts/XAUTH.java rename to src/main/java/fr/xephi/authme/security/crypts/Xauth.java index 9f921b6ae..286295520 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Xauth.java @@ -4,7 +4,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class XAuth extends HexSaltedMethod { +public class Xauth extends HexSaltedMethod { private static String getWhirlpool(String message) { Whirlpool w = new Whirlpool(); diff --git a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java similarity index 100% rename from src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java rename to src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index 04d2bc0bb..cebde5da6 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -9,7 +9,7 @@ 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; +import fr.xephi.authme.security.crypts.Joomla; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; @@ -231,7 +231,7 @@ public class PasswordSecurityTest { ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); verify(pluginManager).callEvent(captor.capture()); PasswordEncryptionEvent event = captor.getValue(); - assertThat(JOOMLA.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(Joomla.class.equals(caughtClassInEvent), equalTo(true)); assertThat(event.getPlayerName(), equalTo(usernameLowerCase)); } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java rename to src/test/java/fr/xephi/authme/security/crypts/BCrypt2yTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java rename to src/test/java/fr/xephi/authme/security/crypts/BCryptTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java b/src/test/java/fr/xephi/authme/security/crypts/CrazyCrypt1Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java rename to src/test/java/fr/xephi/authme/security/crypts/CrazyCrypt1Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/DoubleMd5Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/DOUBLEMD5Test.java rename to src/test/java/fr/xephi/authme/security/crypts/DoubleMd5Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/Ipb3Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Ipb3Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/IPB4Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Ipb4Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java b/src/test/java/fr/xephi/authme/security/crypts/Md5vBTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/MD5VBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/Md5vBTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MyBBTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/MyBBTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/PhpBBTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/PHPBBTest.java rename to src/test/java/fr/xephi/authme/security/crypts/PhpBBTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PhpFusionTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java rename to src/test/java/fr/xephi/authme/security/crypts/PhpFusionTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/RoyalAuthTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/ROYALAUTHTest.java rename to src/test/java/fr/xephi/authme/security/crypts/RoyalAuthTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Salted2Md5Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Salted2Md5Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SaltedSha512Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java rename to src/test/java/fr/xephi/authme/security/crypts/SaltedSha512Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/SMFTest.java b/src/test/java/fr/xephi/authme/security/crypts/SmfTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/SMFTest.java rename to src/test/java/fr/xephi/authme/security/crypts/SmfTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/Wbb3Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Wbb3Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java b/src/test/java/fr/xephi/authme/security/crypts/Wbb4Test.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/WBB4Test.java rename to src/test/java/fr/xephi/authme/security/crypts/Wbb4Test.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java b/src/test/java/fr/xephi/authme/security/crypts/WhirlpoolTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/WHIRLPOOLTest.java rename to src/test/java/fr/xephi/authme/security/crypts/WhirlpoolTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WordpressTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java rename to src/test/java/fr/xephi/authme/security/crypts/WordpressTest.java diff --git a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java b/src/test/java/fr/xephi/authme/security/crypts/XauthTest.java similarity index 84% rename from src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java rename to src/test/java/fr/xephi/authme/security/crypts/XauthTest.java index 877a81ee1..5adbf5790 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XAUTHTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XauthTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link XAuth}. + * Test for {@link Xauth}. */ -public class XAuthTest extends AbstractEncryptionMethodTest { +public class XauthTest extends AbstractEncryptionMethodTest { - public XAuthTest() { - super(new XAuth(), + public XauthTest() { + super(new Xauth(), "e54d4916577410d26d2f6e9362445463dab9ffdff9a67ed3b74d3f2312bc8fab84f653fcb88ad8338793ef8a6d0a1162105e46ec24f0dcb52355c634e3e6439f45444b09c715", // password "d54489a4fd4732ee03d56810ab92944096e3d49335266adeecfbc12567abb3ff744761b33a1fcc4d04739e377775c788e4baace3caf35c7b9176b82b1fe3472e4cbdc5a43214", // PassWord1 "ce6404c1092fb5abf0a72f9c4327bfe8f4cdc4b8dc90ee6ca35c42b8ae9481b89c2559bb60b99ff2b57a102cfced40b8e2f5ef481400c9e6f79445017fc763b1cc27f4c2df36", // &^%te$t?Pw@_ diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java b/src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java similarity index 100% rename from src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java rename to src/test/java/fr/xephi/authme/security/crypts/XfBCryptTest.java From 8f197bbebfd89250371ad0cbdad2d84fbbb0b5ec Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 17 Mar 2017 19:28:40 +0100 Subject: [PATCH 098/125] #1128 Rename converter classes to start with uppercase letter --- .../command/executable/authme/ConverterCommand.java | 8 ++++---- .../{vAuthConverter.java => VAuthConverter.java} | 4 ++-- .../{xAuthConverter.java => XAuthConverter.java} | 4 ++-- src/main/java/fr/xephi/authme/security/HashAlgorithm.java | 2 +- .../authme/security/crypts/{Xauth.java => XAuth.java} | 2 +- .../security/crypts/{XauthTest.java => XAuthTest.java} | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) rename src/main/java/fr/xephi/authme/datasource/converter/{vAuthConverter.java => VAuthConverter.java} (95%) rename src/main/java/fr/xephi/authme/datasource/converter/{xAuthConverter.java => XAuthConverter.java} (98%) rename src/main/java/fr/xephi/authme/security/crypts/{Xauth.java => XAuth.java} (96%) rename src/test/java/fr/xephi/authme/security/crypts/{XauthTest.java => XAuthTest.java} (84%) 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 e1c0661ce..c59c4cb94 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 @@ -10,8 +10,8 @@ import fr.xephi.authme.datasource.converter.MySqlToSqlite; import fr.xephi.authme.datasource.converter.RakamakConverter; 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.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; @@ -78,11 +78,11 @@ public class ConverterCommand implements ExecutableCommand { */ private static Map> getConverters() { return ImmutableMap.>builder() - .put("xauth", xAuthConverter.class) + .put("xauth", XAuthConverter.class) .put("crazylogin", CrazyLoginConverter.class) .put("rakamak", RakamakConverter.class) .put("royalauth", RoyalAuthConverter.class) - .put("vauth", vAuthConverter.class) + .put("vauth", VAuthConverter.class) .put("sqlitetosql", SqliteToSql.class) .put("mysqltosqlite", MySqlToSqlite.class) .build(); diff --git a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java similarity index 95% rename from src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java rename to src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java index 6d3de8b7d..ff88db2d7 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java @@ -16,13 +16,13 @@ import java.util.UUID; import static fr.xephi.authme.util.FileUtils.makePath; -public class vAuthConverter implements Converter { +public class VAuthConverter implements Converter { private final DataSource dataSource; private final File vAuthPasswordsFile; @Inject - vAuthConverter(@DataFolder File dataFolder, DataSource dataSource) { + VAuthConverter(@DataFolder File dataFolder, DataSource dataSource) { vAuthPasswordsFile = new File(dataFolder.getParent(), makePath("vAuth", "passwords.yml")); this.dataSource = dataSource; } diff --git a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java similarity index 98% rename from src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java rename to src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java index af4d5beb7..97979c6c9 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java @@ -21,7 +21,7 @@ import java.util.List; import static fr.xephi.authme.util.FileUtils.makePath; -public class xAuthConverter implements Converter { +public class XAuthConverter implements Converter { @Inject @DataFolder @@ -31,7 +31,7 @@ public class xAuthConverter implements Converter { @Inject private PluginManager pluginManager; - xAuthConverter() { + XAuthConverter() { } @Override diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 75137717c..f12da678d 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -35,7 +35,7 @@ public enum HashAlgorithm { WBB4(fr.xephi.authme.security.crypts.Wbb4.class), WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class), WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class), - XAUTH(fr.xephi.authme.security.crypts.Xauth.class), + XAUTH(fr.xephi.authme.security.crypts.XAuth.class), XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class), CUSTOM(null); diff --git a/src/main/java/fr/xephi/authme/security/crypts/Xauth.java b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java similarity index 96% rename from src/main/java/fr/xephi/authme/security/crypts/Xauth.java rename to src/main/java/fr/xephi/authme/security/crypts/XAuth.java index 286295520..9f921b6ae 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Xauth.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java @@ -4,7 +4,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation; import fr.xephi.authme.security.crypts.description.Usage; @Recommendation(Usage.RECOMMENDED) -public class Xauth extends HexSaltedMethod { +public class XAuth extends HexSaltedMethod { private static String getWhirlpool(String message) { Whirlpool w = new Whirlpool(); diff --git a/src/test/java/fr/xephi/authme/security/crypts/XauthTest.java b/src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java similarity index 84% rename from src/test/java/fr/xephi/authme/security/crypts/XauthTest.java rename to src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java index 5adbf5790..877a81ee1 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/XauthTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/XAuthTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; /** - * Test for {@link Xauth}. + * Test for {@link XAuth}. */ -public class XauthTest extends AbstractEncryptionMethodTest { +public class XAuthTest extends AbstractEncryptionMethodTest { - public XauthTest() { - super(new Xauth(), + public XAuthTest() { + super(new XAuth(), "e54d4916577410d26d2f6e9362445463dab9ffdff9a67ed3b74d3f2312bc8fab84f653fcb88ad8338793ef8a6d0a1162105e46ec24f0dcb52355c634e3e6439f45444b09c715", // password "d54489a4fd4732ee03d56810ab92944096e3d49335266adeecfbc12567abb3ff744761b33a1fcc4d04739e377775c788e4baace3caf35c7b9176b82b1fe3472e4cbdc5a43214", // PassWord1 "ce6404c1092fb5abf0a72f9c4327bfe8f4cdc4b8dc90ee6ca35c42b8ae9481b89c2559bb60b99ff2b57a102cfced40b8e2f5ef481400c9e6f79445017fc763b1cc27f4c2df36", // &^%te$t?Pw@_ From 603217f894d2afef5aa68c4475166daf07692a31 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 17 Mar 2017 19:48:42 +0100 Subject: [PATCH 099/125] Minor - add DS_Store to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 82df141db..da292ac52 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ MANIFEST.MF # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - +# Mac OS +.DS_Store ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm From 7f16e80442ab30ddf02ceaa217da74e937ccaacc Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 18 Mar 2017 20:24:25 +0100 Subject: [PATCH 100/125] Add EqualsHashCode check to checkstyle config - Enables checkstyle verification that hashCode() is overridden if equals(Object) is overridden (and vice versa) --- .checkstyle.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.checkstyle.xml b/.checkstyle.xml index d8f9076d1..2b8f6f930 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -159,6 +159,7 @@ + From 9a9d0974f8720cc36a518bcfaa08bf32f89a5784 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Mar 2017 08:19:52 +0100 Subject: [PATCH 101/125] #1037 Improve architecture of registration methods (#236) * #1037 Improve architecture of registration methods - Introduce parameters classes for each registration method - RegistrationMethod has constants with the required parameters class -> no longer need to have a RegistrationExecutorProvider class that needs to be injected everywhere, let AsyncRegister be the only one to worry about which class will perform the registration - Fix inheritance of password registration types - previously two-factor auth registration inherited from password registration directly * Create matcher which checks for equality via reflections - Allow to perform equality check on objects with default equals() method --- src/main/java/fr/xephi/authme/AuthMe.java | 3 +- src/main/java/fr/xephi/authme/api/NewAPI.java | 12 +- .../executable/register/RegisterCommand.java | 19 +- .../factory/FactoryDependencyHandler.java | 4 +- .../factory/SingletonStore.java | 37 ++++ .../SingletonStoreDependencyHandler.java | 61 +++++++ .../fr/xephi/authme/process/Management.java | 7 +- .../process/register/AsyncRegister.java | 31 ++-- .../AbstractPasswordRegisterExecutor.java | 97 +++++++++++ .../AbstractPasswordRegisterParams.java | 48 +++++ .../ApiPasswordRegisterExecutor.java | 20 +++ .../executors/ApiPasswordRegisterParams.java | 35 ++++ .../executors/EmailRegisterExecutor.java | 80 +++++++++ .../EmailRegisterExecutorProvider.java | 91 ---------- .../executors/EmailRegisterParams.java | 43 +++++ .../executors/PasswordRegisterExecutor.java | 17 ++ .../PasswordRegisterExecutorProvider.java | 164 ------------------ .../executors/PasswordRegisterParams.java | 32 ++++ .../executors/PlayerAuthBuilderHelper.java | 8 + .../executors/RegistrationExecutor.java | 12 +- .../RegistrationExecutorProvider.java | 36 ---- .../executors/RegistrationMethod.java | 51 ++++++ .../executors/RegistrationParameters.java | 28 +++ .../executors/TwoFactorRegisterExecutor.java | 43 +++++ .../executors/TwoFactorRegisterParams.java | 23 +++ .../authme/AuthMeInitializationTest.java | 3 +- .../fr/xephi/authme/ReflectionTestUtils.java | 18 +- .../register/RegisterCommandTest.java | 98 ++++++++--- .../process/register/AsyncRegisterTest.java | 31 +++- .../EmailRegisterExecutorProviderTest.java | 35 ++-- ...java => PasswordRegisterExecutorTest.java} | 26 +-- 31 files changed, 820 insertions(+), 393 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java create mode 100644 src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java delete mode 100644 src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java delete mode 100644 src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java delete mode 100644 src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java create mode 100644 src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java rename src/test/java/fr/xephi/authme/process/register/executors/{PasswordRegisterExecutorProviderTest.java => PasswordRegisterExecutorTest.java} (81%) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 5b21358f4..b66258cb2 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -15,6 +15,7 @@ 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.initialization.factory.SingletonStoreDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.listener.EntityListener; import fr.xephi.authme.listener.PlayerListener; @@ -206,7 +207,7 @@ public class AuthMe extends JavaPlugin { // Create injector, provide elements from the Bukkit environment and register providers injector = new InjectorBuilder() - .addHandlers(new FactoryDependencyHandler()) + .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler()) .addDefaultHandlers("fr.xephi.authme") .create(); injector.register(AuthMe.class, this); diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 2193f64c0..db2c46e0d 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -5,7 +5,8 @@ import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.process.Management; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.service.PluginHookService; @@ -35,15 +36,13 @@ public class NewAPI { private final Management management; private final ValidationService validationService; private final PlayerCache playerCache; - private final RegistrationExecutorProvider registrationExecutorProvider; /* * Constructor for NewAPI. */ @Inject NewAPI(AuthMe plugin, PluginHookService pluginHookService, DataSource dataSource, PasswordSecurity passwordSecurity, - Management management, ValidationService validationService, PlayerCache playerCache, - RegistrationExecutorProvider registrationExecutorProvider) { + Management management, ValidationService validationService, PlayerCache playerCache) { this.plugin = plugin; this.pluginHookService = pluginHookService; this.dataSource = dataSource; @@ -51,7 +50,6 @@ public class NewAPI { this.management = management; this.validationService = validationService; this.playerCache = playerCache; - this.registrationExecutorProvider = registrationExecutorProvider; NewAPI.singleton = this; } @@ -204,8 +202,8 @@ public class NewAPI { * @param autoLogin Should the player be authenticated automatically after the registration? */ public void forceRegister(Player player, String password, boolean autoLogin) { - management.performRegister(player, - registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin)); + management.performRegister(RegistrationMethod.API_REGISTRATION, + ApiPasswordRegisterParams.of(player, password, autoLogin)); } /** 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 6a0a590c0..5b9d75ab1 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 @@ -7,7 +7,10 @@ import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.RegisterSecondaryArgument; import fr.xephi.authme.process.register.RegistrationType; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.EmailRegisterParams; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; @@ -42,15 +45,12 @@ public class RegisterCommand extends PlayerCommand { @Inject private ValidationService validationService; - @Inject - private RegistrationExecutorProvider registrationExecutorProvider; - @Override public void runCommand(Player player, List arguments) { if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) { //for two factor auth we don't need to check the usage - management.performRegister(player, - registrationExecutorProvider.getTwoFactorRegisterExecutor(player)); + management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION, + TwoFactorRegisterParams.of(player)); return; } else if (arguments.size() < 1) { commonService.send(player, MessageKey.USAGE_REGISTER); @@ -82,8 +82,8 @@ public class RegisterCommand extends PlayerCommand { final String password = arguments.get(0); final String email = getEmailIfAvailable(arguments); - management.performRegister( - player, registrationExecutorProvider.getPasswordRegisterExecutor(player, password, email)); + management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION, + PasswordRegisterParams.of(player, password, email)); } } @@ -138,7 +138,8 @@ public class RegisterCommand extends PlayerCommand { if (!validationService.validateEmail(email)) { commonService.send(player, MessageKey.INVALID_EMAIL); } else if (isSecondArgValidForEmailRegistration(player, arguments)) { - management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email)); + management.performRegister(RegistrationMethod.EMAIL_REGISTRATION, + EmailRegisterParams.of(player, email)); } } diff --git a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java index 04c11c68e..e063d1497 100644 --- a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java +++ b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java @@ -16,8 +16,8 @@ public class FactoryDependencyHandler implements DependencyHandler { 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() + "'"); + 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()); diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java new file mode 100644 index 000000000..13a5cbb50 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java @@ -0,0 +1,37 @@ +package fr.xephi.authme.initialization.factory; + +import java.util.Collection; + +/** + * Injectable object to retrieve and create singletons of a common parent. + * + * @param

    the parent class to which this store is limited to + */ +public interface SingletonStore

    { + + /** + * Returns the singleton of the given type, creating it if it hasn't been yet created. + * + * @param clazz the class to get the singleton for + * @param type of the singleton + * @return the singleton of type {@code C} + */ + C getSingleton(Class clazz); + + /** + * Returns all existing singletons of this store's type. + * + * @return all registered singletons of type {@code P} + */ + Collection

    retrieveAllOfType(); + + /** + * Returns all existing singletons of the given type. + * + * @param clazz the type to get singletons for + * @param class type + * @return all registered singletons of type {@code C} + */ + Collection retrieveAllOfType(Class clazz); + +} diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java new file mode 100644 index 000000000..09a198c72 --- /dev/null +++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java @@ -0,0 +1,61 @@ +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; + +import java.util.Collection; + +/** + * Dependency handler that builds {@link SingletonStore} objects. + */ +public class SingletonStoreDependencyHandler implements DependencyHandler { + + @Override + public Object resolveValue(ResolvedInstantiationContext context, DependencyDescription dependencyDescription) { + if (dependencyDescription.getType() == SingletonStore.class) { + Class genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType()); + if (genericType == null) { + throw new IllegalStateException("Singleton store fields must have concrete generic type. " + + "Cannot get generic type for field in '" + context.getMappedClass() + "'"); + } + + return new SingletonStoreImpl<>(genericType, context.getInjector()); + } + return null; + } + + private static final class SingletonStoreImpl

    implements SingletonStore

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

    parentClass; + + SingletonStoreImpl(Class

    parentClass, Injector injector) { + this.parentClass = parentClass; + this.injector = injector; + } + + @Override + public C getSingleton(Class clazz) { + if (parentClass.isAssignableFrom(clazz)) { + return injector.getSingleton(clazz); + } + throw new IllegalArgumentException(clazz + " not child of " + parentClass); + } + + @Override + public Collection

    retrieveAllOfType() { + return retrieveAllOfType(parentClass); + } + + @Override + public Collection retrieveAllOfType(Class clazz) { + if (parentClass.isAssignableFrom(clazz)) { + return injector.retrieveAllOfType(clazz); + } + throw new IllegalArgumentException(clazz + " not child of " + parentClass); + } + } +} diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 8aea8341a..fee949574 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -8,7 +8,8 @@ import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.process.logout.AsynchronousLogout; import fr.xephi.authme.process.quit.AsynchronousQuit; import fr.xephi.authme.process.register.AsyncRegister; -import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.RegistrationParameters; import fr.xephi.authme.process.unregister.AsynchronousUnregister; import fr.xephi.authme.service.BukkitService; import org.bukkit.command.CommandSender; @@ -60,8 +61,8 @@ public class Management { runTask(() -> asynchronousLogout.logout(player)); } - public void performRegister(Player player, RegistrationExecutor registrationExecutor) { - runTask(() -> asyncRegister.register(player, registrationExecutor)); + public

    void performRegister(RegistrationMethod

    variant, P parameters) { + runTask(() -> asyncRegister.register(variant, parameters)); } public void performUnregister(Player player, String password) { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index d32f6eadb..83e5a82cb 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -3,10 +3,13 @@ package fr.xephi.authme.process.register; 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.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationParameters; +import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -31,6 +34,8 @@ public class AsyncRegister implements AsynchronousProcess { private CommonService service; @Inject private PermissionsManager permissionsManager; + @Inject + private SingletonStore registrationExecutorFactory; AsyncRegister() { } @@ -38,12 +43,16 @@ public class AsyncRegister implements AsynchronousProcess { /** * Performs the registration process for the given player. * - * @param player the player to register - * @param executor the registration executor to perform the registration with + * @param variant the registration method + * @param parameters the parameters + * @param

    parameters type */ - public void register(Player player, RegistrationExecutor executor) { - if (preRegisterCheck(player) && executor.isRegistrationAdmitted()) { - executeRegistration(player, executor); + public

    void register(RegistrationMethod

    variant, P parameters) { + if (preRegisterCheck(parameters.getPlayer())) { + RegistrationExecutor

    executor = registrationExecutorFactory.getSingleton(variant.getExecutorClass()); + if (executor.isRegistrationAdmitted(parameters)) { + executeRegistration(parameters, executor); + } } } @@ -66,15 +75,17 @@ public class AsyncRegister implements AsynchronousProcess { /** * Executes the registration. * - * @param player the player to register + * @param parameters the registration parameters * @param executor the executor to perform the registration process with + * @param

    registration params type */ - private void executeRegistration(Player player, RegistrationExecutor executor) { - PlayerAuth auth = executor.buildPlayerAuth(); + private

    + void executeRegistration(P parameters, RegistrationExecutor

    executor) { + PlayerAuth auth = executor.buildPlayerAuth(parameters); if (database.saveAuth(auth)) { - executor.executePostPersistAction(); + executor.executePostPersistAction(parameters); } else { - service.send(player, MessageKey.ERROR); + service.send(parameters.getPlayer(), MessageKey.ERROR); } } diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java new file mode 100644 index 000000000..9f0ee7248 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.process.login.AsynchronousLogin; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.settings.properties.PluginSettings; +import fr.xephi.authme.settings.properties.RegistrationSettings; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +/** + * Registration executor for registration methods where the password + * is supplied by the user. + */ +abstract class AbstractPasswordRegisterExecutor

    + implements RegistrationExecutor

    { + + /** + * Number of ticks to wait before running the login action when it is run synchronously. + * A small delay is necessary or the database won't return the newly saved PlayerAuth object + * and the login process thinks the user is not registered. + */ + private static final int SYNC_LOGIN_DELAY = 5; + + @Inject + private ValidationService validationService; + + @Inject + private CommonService commonService; + + @Inject + private PasswordSecurity passwordSecurity; + + @Inject + private BukkitService bukkitService; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private AsynchronousLogin asynchronousLogin; + + @Override + public boolean isRegistrationAdmitted(P params) { + ValidationService.ValidationResult passwordValidation = validationService.validatePassword( + params.getPassword(), params.getPlayer().getName()); + if (passwordValidation.hasError()) { + commonService.send(params.getPlayer(), passwordValidation.getMessageKey(), passwordValidation.getArgs()); + return false; + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth(P params) { + HashedPassword hashedPassword = passwordSecurity.computeHash(params.getPassword(), params.getPlayerName()); + params.setHashedPassword(hashedPassword); + return createPlayerAuthObject(params); + } + + /** + * Creates the PlayerAuth object to store into the database, based on the registration parameters. + * + * @param params the parameters + * @return the PlayerAuth representing the new account to register + */ + protected abstract PlayerAuth createPlayerAuthObject(P params); + + /** + * Returns whether the player should be automatically logged in after registration. + * + * @param params the registration parameters + * @return true if the player should be logged in, false otherwise + */ + protected boolean performLoginAfterRegister(P params) { + return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); + } + + @Override + public void executePostPersistAction(P params) { + final Player player = params.getPlayer(); + if (performLoginAfterRegister(params)) { + if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) { + bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); + } else { + bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); + } + } + syncProcessManager.processSyncPasswordRegister(player); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java new file mode 100644 index 000000000..0c7a1d515 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.security.crypts.HashedPassword; +import org.bukkit.entity.Player; + +/** + * Common params type for implementors of {@link AbstractPasswordRegisterExecutor}. + * Password must be supplied on creation and cannot be changed later on. The {@link HashedPassword} + * is stored on the params object for later use. + */ +public abstract class AbstractPasswordRegisterParams extends RegistrationParameters { + + private final String password; + private HashedPassword hashedPassword; + + /** + * Constructor. + * + * @param player the player to register + * @param password the password to use + */ + public AbstractPasswordRegisterParams(Player player, String password) { + super(player); + this.password = password; + } + + /** + * Constructor with no defined password. Use for registration methods which + * have no implicit password (like two factor authentication). + * + * @param player the player to register + */ + public AbstractPasswordRegisterParams(Player player) { + this(player, null); + } + + public String getPassword() { + return password; + } + + void setHashedPassword(HashedPassword hashedPassword) { + this.hashedPassword = hashedPassword; + } + + HashedPassword getHashedPassword() { + return hashedPassword; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java new file mode 100644 index 000000000..6005c8e41 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; + +/** + * Executor for password registration via API call. + */ +class ApiPasswordRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Override + protected PlayerAuth createPlayerAuthObject(ApiPasswordRegisterParams params) { + return PlayerAuthBuilderHelper + .createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null); + } + + @Override + protected boolean performLoginAfterRegister(ApiPasswordRegisterParams params) { + return params.getLoginAfterRegister(); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java new file mode 100644 index 000000000..357d32c22 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java @@ -0,0 +1,35 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for {@link ApiPasswordRegisterExecutor}. + */ +public class ApiPasswordRegisterParams extends PasswordRegisterParams { + + private final boolean loginAfterRegister; + + protected ApiPasswordRegisterParams(Player player, String password, boolean loginAfterRegister) { + super(player, password, null); + this.loginAfterRegister = loginAfterRegister; + } + + /** + * Creates a parameters object. + * + * @param player the player to register + * @param password the password to register with + * @param loginAfterRegister whether the player should be logged in after registration + * @return params object with the given data + */ + public static ApiPasswordRegisterParams of(Player player, String password, boolean loginAfterRegister) { + return new ApiPasswordRegisterParams(player, password, loginAfterRegister); + } + + /** + * @return true if the player should be logged in after being registered, false otherwise + */ + public boolean getLoginAfterRegister() { + return loginAfterRegister; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java new file mode 100644 index 000000000..0f4153b18 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java @@ -0,0 +1,80 @@ +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.EmailService; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.SyncProcessManager; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.settings.properties.EmailSettings; +import fr.xephi.authme.util.RandomStringUtils; +import org.bukkit.entity.Player; + +import javax.inject.Inject; + +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; + +/** + * Executor for email registration: the player only provides his email address, + * to which a generated password is sent. + */ +class EmailRegisterExecutor implements RegistrationExecutor { + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private DataSource dataSource; + + @Inject + private CommonService commonService; + + @Inject + private EmailService emailService; + + @Inject + private SyncProcessManager syncProcessManager; + + @Inject + private PasswordSecurity passwordSecurity; + + @Override + public boolean isRegistrationAdmitted(EmailRegisterParams params) { + final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL); + if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(params.getPlayer(), ALLOW_MULTIPLE_ACCOUNTS)) { + int otherAccounts = dataSource.countAuthsByEmail(params.getEmail()); + if (otherAccounts >= maxRegPerEmail) { + commonService.send(params.getPlayer(), MessageKey.MAX_REGISTER_EXCEEDED, + Integer.toString(maxRegPerEmail), Integer.toString(otherAccounts), "@"); + return false; + } + } + return true; + } + + @Override + public PlayerAuth buildPlayerAuth(EmailRegisterParams params) { + String password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); + HashedPassword hashedPassword = passwordSecurity.computeHash(password, params.getPlayer().getName()); + params.setPassword(password); + return createPlayerAuth(params.getPlayer(), hashedPassword, params.getEmail()); + } + + @Override + public void executePostPersistAction(EmailRegisterParams params) { + Player player = params.getPlayer(); + boolean couldSendMail = emailService.sendPasswordMail( + player.getName(), params.getEmail(), params.getPassword()); + if (couldSendMail) { + syncProcessManager.processSyncEmailRegister(player); + } else { + commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); + } + } + +} 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 deleted file mode 100644 index 7a6e702d3..000000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java +++ /dev/null @@ -1,91 +0,0 @@ -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.EmailService; -import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.process.SyncProcessManager; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.settings.properties.EmailSettings; -import fr.xephi.authme.util.RandomStringUtils; -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -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; - -/** - * Provides a registration executor for email registration. - */ -class EmailRegisterExecutorProvider { - - @Inject - private PermissionsManager permissionsManager; - - @Inject - private DataSource dataSource; - - @Inject - private CommonService commonService; - - @Inject - private EmailService emailService; - - @Inject - private SyncProcessManager syncProcessManager; - - @Inject - private PasswordSecurity passwordSecurity; - - EmailRegisterExecutorProvider() { - } - - /** Registration executor implementation for email registration. */ - class EmailRegisterExecutor implements RegistrationExecutor { - - private final Player player; - private final String email; - private String password; - - EmailRegisterExecutor(Player player, String email) { - this.player = player; - this.email = email; - } - - @Override - public boolean isRegistrationAdmitted() { - final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL); - if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) { - int otherAccounts = dataSource.countAuthsByEmail(email); - if (otherAccounts >= maxRegPerEmail) { - commonService.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail), - Integer.toString(otherAccounts), "@"); - return false; - } - } - return true; - } - - @Override - public PlayerAuth buildPlayerAuth() { - password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); - HashedPassword hashedPassword = passwordSecurity.computeHash(password, player.getName()); - return createPlayerAuth(player, hashedPassword, email); - } - - @Override - public void executePostPersistAction() { - boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password); - if (couldSendMail) { - syncProcessManager.processSyncEmailRegister(player); - } else { - commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); - } - } - } -} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java new file mode 100644 index 000000000..94d03acc5 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for email registration. + */ +public class EmailRegisterParams extends RegistrationParameters { + + private final String email; + private String password; + + protected EmailRegisterParams(Player player, String email) { + super(player); + this.email = email; + } + + /** + * Creates a params object for email registration. + * + * @param player the player to register + * @param email the player's email + * @return params object with the given data + */ + public static EmailRegisterParams of(Player player, String email) { + return new EmailRegisterParams(player, email); + } + + public String getEmail() { + return email; + } + + void setPassword(String password) { + this.password = password; + } + + /** + * @return the password generated for the player + */ + String getPassword() { + return password; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java new file mode 100644 index 000000000..167bee292 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; + +/** + * Registration executor for password registration. + */ +class PasswordRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Override + public PlayerAuth createPlayerAuthObject(PasswordRegisterParams params) { + return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), params.getEmail()); + } + +} 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 deleted file mode 100644 index 9d40bcf82..000000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java +++ /dev/null @@ -1,164 +0,0 @@ -package fr.xephi.authme.process.register.executors; - -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.message.MessageKey; -import fr.xephi.authme.process.SyncProcessManager; -import fr.xephi.authme.process.login.AsynchronousLogin; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.security.crypts.TwoFactor; -import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.CommonService; -import fr.xephi.authme.service.ValidationService; -import fr.xephi.authme.service.ValidationService.ValidationResult; -import fr.xephi.authme.settings.properties.PluginSettings; -import fr.xephi.authme.settings.properties.RegistrationSettings; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; - -/** - * Provides registration executors for password-based registration variants. - */ -class PasswordRegisterExecutorProvider { - - /** - * Number of ticks to wait before running the login action when it is run synchronously. - * A small delay is necessary or the database won't return the newly saved PlayerAuth object - * and the login process thinks the user is not registered. - */ - private static final int SYNC_LOGIN_DELAY = 5; - - @Inject - private ValidationService validationService; - - @Inject - private CommonService commonService; - - @Inject - private PasswordSecurity passwordSecurity; - - @Inject - private BukkitService bukkitService; - - @Inject - private SyncProcessManager syncProcessManager; - - @Inject - private AsynchronousLogin asynchronousLogin; - - PasswordRegisterExecutorProvider() { - } - - /** Registration executor for password registration. */ - class PasswordRegisterExecutor implements RegistrationExecutor { - - private final Player player; - private final String password; - private final String email; - private HashedPassword hashedPassword; - - /** - * Constructor. - * - * @param player the player to register - * @param password the password to register with - * @param email the email of the player (may be null) - */ - PasswordRegisterExecutor(Player player, String password, String email) { - this.player = player; - this.password = password; - this.email = email; - } - - @Override - public boolean isRegistrationAdmitted() { - ValidationResult passwordValidation = validationService.validatePassword(password, player.getName()); - if (passwordValidation.hasError()) { - commonService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs()); - return false; - } - return true; - } - - @Override - public PlayerAuth buildPlayerAuth() { - hashedPassword = passwordSecurity.computeHash(password, player.getName().toLowerCase()); - return createPlayerAuth(player, hashedPassword, email); - } - - protected boolean performLoginAfterRegister() { - return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER); - } - - @Override - public void executePostPersistAction() { - if (performLoginAfterRegister()) { - if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) { - bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player)); - } else { - bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY); - } - } - syncProcessManager.processSyncPasswordRegister(player); - } - - protected Player getPlayer() { - return player; - } - - protected HashedPassword getHashedPassword() { - return hashedPassword; - } - } - - /** Executor for password registration via API call. */ - class ApiPasswordRegisterExecutor extends PasswordRegisterExecutor { - - private final boolean loginAfterRegister; - - /** - * Constructor. - * - * @param player the player to register - * @param password the password to register with - * @param loginAfterRegister whether the user should be automatically logged in after registration - */ - ApiPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { - super(player, password, null); - this.loginAfterRegister = loginAfterRegister; - } - - @Override - protected boolean performLoginAfterRegister() { - return loginAfterRegister; - } - } - - /** Executor for two factor registration. */ - class TwoFactorRegisterExecutor extends PasswordRegisterExecutor { - - TwoFactorRegisterExecutor(Player player) { - super(player, "", null); - } - - @Override - public boolean isRegistrationAdmitted() { - // nothing to check - return true; - } - - @Override - public void executePostPersistAction() { - super.executePostPersistAction(); - - 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/process/register/executors/PasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java new file mode 100644 index 000000000..f21861bff --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java @@ -0,0 +1,32 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for registration with a given password, and optionally an email address. + */ +public class PasswordRegisterParams extends AbstractPasswordRegisterParams { + + private final String email; + + protected PasswordRegisterParams(Player player, String password, String email) { + super(player, password); + this.email = email; + } + + /** + * Creates a params object. + * + * @param player the player to register + * @param password the password to register with + * @param email the email of the player (may be null) + * @return params object with the given data + */ + public static PasswordRegisterParams of(Player player, String password, String email) { + return new PasswordRegisterParams(player, password, email); + } + + public String getEmail() { + return email; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java index 1943b5d54..5d9ab8657 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java @@ -13,6 +13,14 @@ final class PlayerAuthBuilderHelper { private PlayerAuthBuilderHelper() { } + /** + * Creates a {@link PlayerAuth} object with the given data. + * + * @param player the player to create a PlayerAuth for + * @param hashedPassword the hashed password + * @param email the email address (nullable) + * @return the generated PlayerAuth object + */ static PlayerAuth createPlayerAuth(Player player, HashedPassword hashedPassword, String email) { return PlayerAuth.builder() .name(player.getName().toLowerCase()) diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java index cceb5c18e..ad18bf924 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java @@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth; /** * Performs the registration action. */ -public interface RegistrationExecutor { +public interface RegistrationExecutor

    { /** * Returns whether the registration may take place. Use this method to execute @@ -14,20 +14,24 @@ public interface RegistrationExecutor { * If this method returns {@code false}, it is expected that the executor inform * the player about the error within this method call. * + * @param params the parameters for the registration * @return true if registration may be performed, false otherwise */ - boolean isRegistrationAdmitted(); + boolean isRegistrationAdmitted(P params); /** * Constructs the PlayerAuth object to persist into the database. * + * @param params the parameters for the registration * @return the player auth to register in the data source */ - PlayerAuth buildPlayerAuth(); + PlayerAuth buildPlayerAuth(P params); /** * Follow-up method called after the player auth could be added into the database. + * + * @param params the parameters for the registration */ - void executePostPersistAction(); + void executePostPersistAction(P params); } diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java deleted file mode 100644 index 286a51634..000000000 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package fr.xephi.authme.process.register.executors; - -import org.bukkit.entity.Player; - -import javax.inject.Inject; - -/** - * Provides a {@link RegistrationExecutor} for various registration methods. - */ -public class RegistrationExecutorProvider { - - @Inject - private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; - - @Inject - private EmailRegisterExecutorProvider emailRegisterExecutorProvider; - - RegistrationExecutorProvider() { - } - - public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, String email) { - return passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, email); - } - - public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) { - return passwordRegisterExecutorProvider.new ApiPasswordRegisterExecutor(player, password, loginAfterRegister); - } - - public RegistrationExecutor getTwoFactorRegisterExecutor(Player player) { - return passwordRegisterExecutorProvider.new TwoFactorRegisterExecutor(player); - } - - public RegistrationExecutor getEmailRegisterExecutor(Player player, String email) { - return emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); - } -} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java new file mode 100644 index 000000000..9232332ef --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java @@ -0,0 +1,51 @@ +package fr.xephi.authme.process.register.executors; + +/** + * Methods with which a player can be registered. + *

    + * These constants each define a different way of registering a player and define the + * {@link RegistrationParameters parameters} and {@link RegistrationExecutor executor} + * classes which perform this registration method. This is essentially a typed enum + * as passing a constant of this class along with a parameters object to a method can + * be restricted to the correct parameters type. + */ +public final class RegistrationMethod

    { + + /** + * Password registration. + */ + public static final RegistrationMethod PASSWORD_REGISTRATION = + new RegistrationMethod<>(PasswordRegisterExecutor.class); + + /** + * Registration with two-factor authentication as login means. + */ + public static final RegistrationMethod TWO_FACTOR_REGISTRATION = + new RegistrationMethod<>(TwoFactorRegisterExecutor.class); + + /** + * Email registration: an email address is provided, to which a generated password is sent. + */ + public static final RegistrationMethod EMAIL_REGISTRATION = + new RegistrationMethod<>(EmailRegisterExecutor.class); + + /** + * API registration: player and password are provided via an API method. + */ + public static final RegistrationMethod API_REGISTRATION = + new RegistrationMethod<>(ApiPasswordRegisterExecutor.class); + + + private final Class> executorClass; + + private RegistrationMethod(Class> executorClass) { + this.executorClass = executorClass; + } + + /** + * @return the executor class to perform the registration method + */ + public Class> getExecutorClass() { + return executorClass; + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java new file mode 100644 index 000000000..c92d57ffa --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java @@ -0,0 +1,28 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parent of all registration parameters. + */ +public abstract class RegistrationParameters { + + private final Player player; + + /** + * Constructor. + * + * @param player the player to perform the registration for + */ + public RegistrationParameters(Player player) { + this.player = player; + } + + public Player getPlayer() { + return player; + } + + public String getPlayerName() { + return player.getName(); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java new file mode 100644 index 000000000..027a5fa68 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java @@ -0,0 +1,43 @@ +package fr.xephi.authme.process.register.executors; + +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.security.crypts.TwoFactor; +import fr.xephi.authme.service.CommonService; +import org.bukkit.Bukkit; + +import javax.inject.Inject; + +import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth; + +/** + * Executor for two-factor registration. + */ +class TwoFactorRegisterExecutor extends AbstractPasswordRegisterExecutor { + + @Inject + private CommonService commonService; + + @Override + public boolean isRegistrationAdmitted(TwoFactorRegisterParams params) { + // nothing to check + return true; + } + + @Override + protected PlayerAuth createPlayerAuthObject(TwoFactorRegisterParams params) { + return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null); + } + + @Override + public void executePostPersistAction(TwoFactorRegisterParams params) { + super.executePostPersistAction(params); + + // Note ljacqu 20170317: This two-factor registration type is only invoked when the password hash is configured + // to two-factor authentication. Therefore, the hashed password is the result of the TwoFactor EncryptionMethod + // implementation (contains the TOTP secret). + String hash = params.getHashedPassword().getHash(); + String qrCodeUrl = TwoFactor.getQRBarcodeURL(params.getPlayerName(), Bukkit.getIp(), hash); + commonService.send(params.getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl); + } +} diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java new file mode 100644 index 000000000..a7a758750 --- /dev/null +++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.process.register.executors; + +import org.bukkit.entity.Player; + +/** + * Parameters for registration with two-factor authentication. + */ +public class TwoFactorRegisterParams extends AbstractPasswordRegisterParams { + + protected TwoFactorRegisterParams(Player player) { + super(player); + } + + /** + * Creates a parameters object. + * + * @param player the player to register + * @return params object with the given player + */ + public static TwoFactorRegisterParams of(Player player) { + return new TwoFactorRegisterParams(player); + } +} diff --git a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java index 89132fcfa..f11d7311a 100644 --- a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java +++ b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java @@ -9,6 +9,7 @@ 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.initialization.factory.SingletonStoreDependencyHandler; import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; @@ -93,7 +94,7 @@ public class AuthMeInitializationTest { new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData()); Injector injector = new InjectorBuilder() - .addHandlers(new FactoryDependencyHandler()) + .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler()) .addDefaultHandlers("fr.xephi.authme") .create(); injector.provide(DataFolder.class, dataFolder); diff --git a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java index fdd716bd5..a4392a325 100644 --- a/src/test/java/fr/xephi/authme/ReflectionTestUtils.java +++ b/src/test/java/fr/xephi/authme/ReflectionTestUtils.java @@ -25,7 +25,7 @@ public final class ReflectionTestUtils { */ public static void setField(Class clazz, T instance, String fieldName, Object value) { try { - Field field = getField(clazz, instance, fieldName); + Field field = getField(clazz, fieldName); field.set(instance, value); } catch (IllegalAccessException e) { throw new UnsupportedOperationException( @@ -34,24 +34,30 @@ public final class ReflectionTestUtils { } } - private static Field getField(Class clazz, T instance, String fieldName) { + private static Field getField(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field; } catch (NoSuchFieldException e) { - throw new UnsupportedOperationException(format("Could not get field '%s' for instance '%s' of class '%s'", - fieldName, instance, clazz.getName()), e); + throw new UnsupportedOperationException(format("Could not get field '%s' from class '%s'", + fieldName, clazz.getName()), e); } } @SuppressWarnings("unchecked") public static V getFieldValue(Class clazz, T instance, String fieldName) { - Field field = getField(clazz, instance, fieldName); + Field field = getField(clazz, fieldName); + return getFieldValue(field, instance); + } + + @SuppressWarnings("unchecked") + public static V getFieldValue(Field field, Object instance) { + field.setAccessible(true); try { return (V) field.get(instance); } catch (IllegalAccessException e) { - throw new UnsupportedOperationException("Could not get value of field '" + fieldName + "'", e); + throw new UnsupportedOperationException("Could not get value of field '" + field.getName() + "'", e); } } 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 c87252e1c..6224a6065 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,13 +1,17 @@ package fr.xephi.authme.command.executable.register; +import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; 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; import fr.xephi.authme.process.register.RegistrationType; -import fr.xephi.authme.process.register.executors.RegistrationExecutor; -import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider; +import fr.xephi.authme.process.register.executors.EmailRegisterParams; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.RegistrationParameters; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.ValidationService; @@ -16,6 +20,9 @@ import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -24,10 +31,16 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Objects; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -57,9 +70,6 @@ public class RegisterCommandTest { @Mock private ValidationService validationService; - @Mock - private RegistrationExecutorProvider registrationExecutorProvider; - @BeforeClass public static void setup() { TestHelper.setupLogger(); @@ -90,14 +100,13 @@ public class RegisterCommandTest { // given given(commonService.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.TWO_FACTOR); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getTwoFactorRegisterExecutor(player)).willReturn(executor); // when command.executeCommand(player, Collections.emptyList()); // then - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.TWO_FACTOR_REGISTRATION), + argThat(isEqualTo(TwoFactorRegisterParams.of(player)))); verifyZeroInteractions(emailService); } @@ -208,8 +217,6 @@ public class RegisterCommandTest { given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.CONFIRMATION); given(emailService.hasAllInformation()).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getEmailRegisterExecutor(player, playerMail)).willReturn(executor); // when command.executeCommand(player, Arrays.asList(playerMail, playerMail)); @@ -217,7 +224,8 @@ public class RegisterCommandTest { // then verify(validationService).validateEmail(playerMail); verify(emailService).hasAllInformation(); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.EMAIL_REGISTRATION), + argThat(isEqualTo(EmailRegisterParams.of(player, playerMail)))); } @Test @@ -239,14 +247,13 @@ public class RegisterCommandTest { public void shouldPerformPasswordRegistration() { // given Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", null)).willReturn(executor); // when command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null)))); } @Test @@ -257,15 +264,14 @@ public class RegisterCommandTest { String email = "email@example.org"; given(validationService.validateEmail(email)).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(player, "myPass", email)).willReturn(executor); // when command.executeCommand(player, Arrays.asList("myPass", email)); // then verify(validationService).validateEmail(email); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", email)))); } @Test @@ -292,14 +298,64 @@ public class RegisterCommandTest { given(commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE)).willReturn(RegistrationType.PASSWORD); given(commonService.getProperty(RegistrationSettings.REGISTER_SECOND_ARGUMENT)).willReturn(RegisterSecondaryArgument.EMAIL_OPTIONAL); Player player = mock(Player.class); - RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(registrationExecutorProvider.getPasswordRegisterExecutor(eq(player), anyString(), eq(null))).willReturn(executor); // when command.executeCommand(player, Collections.singletonList("myPass")); // then - verify(registrationExecutorProvider).getPasswordRegisterExecutor(player, "myPass", null); - verify(management).performRegister(player, executor); + verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), + argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null)))); + } + + + // TODO ljacqu 20170317: Document and extract as util + + private static

    Matcher

    isEqualTo(P expected) { + return new TypeSafeMatcher

    () { + @Override + protected boolean matchesSafely(RegistrationParameters item) { + assertAreParamsEqual(expected, item); + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("parameters " + expected); + } + }; + } + + private static void assertAreParamsEqual(RegistrationParameters lhs, RegistrationParameters rhs) { + if (lhs.getClass() != rhs.getClass()) { + fail("Params classes don't match, got " + lhs.getClass().getSimpleName() + + " and " + rhs.getClass().getSimpleName()); + } + + List fieldsToCheck = getFields(lhs); + for (Field field : fieldsToCheck) { + Object lhsValue = ReflectionTestUtils.getFieldValue(field, lhs); + Object rhsValue = ReflectionTestUtils.getFieldValue(field, rhs); + if (!Objects.equals(lhsValue, rhsValue)) { + fail("Field '" + field.getName() + "' does not have same value: '" + + lhsValue + "' vs. '" + rhsValue + "'"); + } + } + } + + private static List getFields(RegistrationParameters params) { + List fields = new ArrayList<>(); + Class currentClass = params.getClass(); + while (currentClass != null) { + for (Field f : currentClass.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + fields.add(f); + } + } + if (currentClass == RegistrationParameters.class) { + break; + } + currentClass = currentClass.getSuperclass(); + } + return fields; } } diff --git a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java index b845b8d59..dee3dc646 100644 --- a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java +++ b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java @@ -3,9 +3,13 @@ package fr.xephi.authme.process.register; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.process.register.executors.PasswordRegisterParams; import fr.xephi.authme.process.register.executors.RegistrationExecutor; +import fr.xephi.authme.process.register.executors.RegistrationMethod; +import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; @@ -16,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; @@ -39,6 +44,8 @@ public class AsyncRegisterTest { private CommonService commonService; @Mock private DataSource dataSource; + @Mock + private SingletonStore registrationExecutorStore; @Test public void shouldDetectAlreadyLoggedInPlayer() { @@ -47,9 +54,10 @@ public class AsyncRegisterTest { Player player = mockPlayerWithName(name); given(playerCache.isAuthenticated(name)).willReturn(true); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.PASSWORD_REGISTRATION, PasswordRegisterParams.of(player, "abc", null)); // then verify(commonService).send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); @@ -64,9 +72,10 @@ public class AsyncRegisterTest { given(playerCache.isAuthenticated(name)).willReturn(false); given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(false); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player)); // then verify(commonService).send(player, MessageKey.REGISTRATION_DISABLED); @@ -82,9 +91,10 @@ public class AsyncRegisterTest { given(commonService.getProperty(RegistrationSettings.IS_ENABLED)).willReturn(true); given(dataSource.isAuthAvailable(name)).willReturn(true); RegistrationExecutor executor = mock(RegistrationExecutor.class); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, TwoFactorRegisterParams.of(player)); // then verify(commonService).send(player, MessageKey.NAME_ALREADY_REGISTERED); @@ -93,6 +103,7 @@ public class AsyncRegisterTest { } @Test + @SuppressWarnings("unchecked") public void shouldStopForFailedExecutorCheck() { // given String name = "edbert"; @@ -103,14 +114,16 @@ public class AsyncRegisterTest { given(commonService.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP)).willReturn(0); given(dataSource.isAuthAvailable(name)).willReturn(false); RegistrationExecutor executor = mock(RegistrationExecutor.class); - given(executor.isRegistrationAdmitted()).willReturn(false); + TwoFactorRegisterParams params = TwoFactorRegisterParams.of(player); + given(executor.isRegistrationAdmitted(params)).willReturn(false); + singletonStoreWillReturn(registrationExecutorStore, executor); // when - asyncRegister.register(player, executor); + asyncRegister.register(RegistrationMethod.TWO_FACTOR_REGISTRATION, params); // then verify(dataSource, only()).isAuthAvailable(name); - verify(executor, only()).isRegistrationAdmitted(); + verify(executor, only()).isRegistrationAdmitted(params); } private static Player mockPlayerWithName(String name) { @@ -118,4 +131,10 @@ public class AsyncRegisterTest { given(player.getName()).willReturn(name); return player; } + + @SuppressWarnings("unchecked") + private static void singletonStoreWillReturn(SingletonStore store, + RegistrationExecutor mock) { + given(store.getSingleton(any(Class.class))).willReturn(mock); + } } 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 9ca9c7aa8..6aa221611 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 @@ -1,6 +1,5 @@ package fr.xephi.authme.process.register.executors; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; @@ -34,13 +33,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Test for {@link EmailRegisterExecutorProvider}. + * Test for {@link EmailRegisterExecutor}. */ @RunWith(MockitoJUnitRunner.class) public class EmailRegisterExecutorProviderTest { @InjectMocks - private EmailRegisterExecutorProvider emailRegisterExecutorProvider; + private EmailRegisterExecutor executor; @Mock private PermissionsManager permissionsManager; @@ -62,10 +61,10 @@ public class EmailRegisterExecutorProviderTest { String email = "test@example.com"; given(dataSource.countAuthsByEmail(email)).willReturn(4); Player player = mock(Player.class); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email); + EmailRegisterParams params = EmailRegisterParams.of(player, email); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(false)); @@ -80,10 +79,10 @@ public class EmailRegisterExecutorProviderTest { given(commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL)).willReturn(3); Player player = mock(Player.class); given(permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)).willReturn(true); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -97,10 +96,10 @@ public class EmailRegisterExecutorProviderTest { String email = "test@example.com"; given(dataSource.countAuthsByEmail(email)).willReturn(0); Player player = mock(Player.class); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -120,10 +119,10 @@ public class EmailRegisterExecutorProviderTest { World world = mock(World.class); given(world.getName()).willReturn("someWorld"); given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); - RegistrationExecutor executor = emailRegisterExecutorProvider.new EmailRegisterExecutor(player, "test@example.com"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); // when - PlayerAuth auth = executor.buildPlayerAuth(); + PlayerAuth auth = executor.buildPlayerAuth(params); // then assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", "123.45.67.89")); @@ -132,18 +131,17 @@ public class EmailRegisterExecutorProviderTest { } @Test - @SuppressWarnings("unchecked") public void shouldPerformActionAfterDataSourceSave() { // given 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"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); String password = "A892C#@"; - ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + params.setPassword(password); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); @@ -151,18 +149,17 @@ public class EmailRegisterExecutorProviderTest { } @Test - @SuppressWarnings("unchecked") public void shouldHandleEmailSendingFailure() { // given 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"); + EmailRegisterParams params = EmailRegisterParams.of(player, "test@example.com"); String password = "A892C#@"; - ReflectionTestUtils.setField((Class) executor.getClass(), executor, "password", password); + params.setPassword(password); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verify(emailService).sendPasswordMail("Laleh", "test@example.com", password); diff --git a/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java similarity index 81% rename from src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java rename to src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java index a033975f6..6c103944b 100644 --- a/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProviderTest.java +++ b/src/test/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorTest.java @@ -34,13 +34,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; /** - * Test for {@link PasswordRegisterExecutorProvider}. + * Test for {@link PasswordRegisterExecutor}. */ @RunWith(MockitoJUnitRunner.class) -public class PasswordRegisterExecutorProviderTest { +public class PasswordRegisterExecutorTest { @InjectMocks - private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider; + private PasswordRegisterExecutor executor; @Mock private ValidationService validationService; @@ -62,10 +62,10 @@ public class PasswordRegisterExecutorProviderTest { String name = "player040"; given(validationService.validatePassword(password, name)).willReturn(new ValidationResult()); Player player = mockPlayerWithName(name); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(true)); @@ -80,10 +80,10 @@ public class PasswordRegisterExecutorProviderTest { given(validationService.validatePassword(password, name)).willReturn( new ValidationResult(MessageKey.PASSWORD_CHARACTERS_ERROR, "[a-z]")); Player player = mockPlayerWithName(name); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, null); + PasswordRegisterParams params = PasswordRegisterParams.of(player, password, null); // when - boolean result = executor.isRegistrationAdmitted(); + boolean result = executor.isRegistrationAdmitted(params); // then assertThat(result, equalTo(false)); @@ -101,10 +101,10 @@ public class PasswordRegisterExecutorProviderTest { World world = mock(World.class); given(world.getName()).willReturn("someWorld"); given(player.getLocation()).willReturn(new Location(world, 48, 96, 144)); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - PlayerAuth auth = executor.buildPlayerAuth(); + PlayerAuth auth = executor.buildPlayerAuth(params); // then assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", "123.45.67.89")); @@ -118,10 +118,10 @@ public class PasswordRegisterExecutorProviderTest { given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(false); given(commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)).willReturn(false); Player player = mock(Player.class); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then TestHelper.runSyncDelayedTaskWithDelay(bukkitService); @@ -134,10 +134,10 @@ public class PasswordRegisterExecutorProviderTest { // given given(commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER)).willReturn(true); Player player = mock(Player.class); - RegistrationExecutor executor = passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, "pass", "mail@example.org"); + PasswordRegisterParams params = PasswordRegisterParams.of(player, "pass", "mail@example.org"); // when - executor.executePostPersistAction(); + executor.executePostPersistAction(params); // then verifyZeroInteractions(bukkitService, asynchronousLogin); From 3c45ca8425b4d7669ad95cc9719db8fb8d7b52fb Mon Sep 17 00:00:00 2001 From: ljacqu Date: Mon, 20 Mar 2017 20:57:09 +0100 Subject: [PATCH 102/125] #1100 Check that auth exists for /email show - This case should never happen, but better to be safe --- .../xephi/authme/command/executable/email/ShowEmailCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java index 151236e1a..24a300ef8 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java @@ -24,7 +24,7 @@ public class ShowEmailCommand extends PlayerCommand { @Override public void runCommand(Player player, List arguments) { PlayerAuth auth = playerCache.getAuth(player.getName()); - if (auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) { + if (auth != null && auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) { commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail()); } else { commonService.send(player, MessageKey.SHOW_NO_EMAIL); From ed0126d06cb995b140ab8bd232ab0c0df03ad6b5 Mon Sep 17 00:00:00 2001 From: Gnat008 Date: Tue, 21 Mar 2017 16:48:20 -0400 Subject: [PATCH 103/125] Add a couple tests --- .../service/PasswordRecoveryServiceTest.java | 35 ++++++++++++++----- .../util/expiring/TimedCounterTest.java | 15 ++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java b/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java index c7c1419d1..48973bbfa 100644 --- a/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/PasswordRecoveryServiceTest.java @@ -1,24 +1,27 @@ package fr.xephi.authme.service; import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; import ch.jalu.injector.testing.InjectDelayed; 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.settings.properties.SecuritySettings; -import org.junit.Ignore; +import org.bukkit.entity.Player; +import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link PasswordRecoveryService}. */ -@Ignore -@RunWith(MockitoJUnitRunner.class) +@RunWith(DelayedInjectionRunner.class) public class PasswordRecoveryServiceTest { @InjectDelayed @@ -39,16 +42,32 @@ public class PasswordRecoveryServiceTest { @Mock private PasswordSecurity passwordSecurity; - @Mock - private RecoveryCodeService recoveryCodeService; - @Mock private Messages messages; @BeforeInjecting public void initSettings() { given(commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS)).willReturn(40); + given(commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT)).willReturn(2); } - //TODO: Write tests + @Test + public void shouldSendRecoveryCode() { + // given + Player player = mock(Player.class); + String name = "Carl"; + given(player.getName()).willReturn(name); + String email = "test@example.com"; + String code = "qwerty"; + given(codeService.generateCode(name)).willReturn(code); + given(emailService.sendRecoveryCode(player.getName(), email, code)).willReturn(true); + + // when + recoveryService.createAndSendRecoveryCode(player, email); + + // then + verify(codeService).generateCode(name); + verify(emailService).sendRecoveryCode(name, email, code); + verify(commonService).send(player, MessageKey.RECOVERY_CODE_SENT); + } } diff --git a/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java b/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java index dcfcb73af..abf012744 100644 --- a/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java +++ b/src/test/java/fr/xephi/authme/util/expiring/TimedCounterTest.java @@ -37,6 +37,21 @@ public class TimedCounterTest { assertThat(counter.get("moto"), equalTo(13)); } + @Test + public void shouldDecrementCount() { + // given + TimedCounter counter = new TimedCounter<>(10, TimeUnit.MINUTES); + counter.put("moto", 12); + + // when + counter.decrement("hello"); + counter.decrement("moto"); + + // then + assertThat(counter.get("hello"), equalTo(0)); + assertThat(counter.get("moto"), equalTo(11)); + } + @Test public void shouldSumUpEntries() { // given From d19748fe5bc625c42f2cb772319aa222dfe2f10b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 21 Mar 2017 22:00:21 +0100 Subject: [PATCH 104/125] #1034 Debug commands: permission checker + data statistics - Create debug command to check if a player has the given permission - Create debug command that outputs the size of various caches / DB info / number of saved instances in injector --- .../authme/debug/DataStatistics.java | 72 ++++++++++ .../executable/authme/debug/DebugCommand.java | 6 +- .../authme/debug/DebugSectionUtils.java | 43 ++++++ .../authme/debug/HasPermissionChecker.java | 130 ++++++++++++++++++ .../authme/debug/LimboPlayerViewer.java | 38 +---- .../authme/debug/DebugSectionUtilsTest.java | 23 ++++ .../debug/HasPermissionCheckerTest.java | 97 +++++++++++++ 7 files changed, 370 insertions(+), 39 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java create mode 100644 src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java new file mode 100644 index 000000000..3bf28e057 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.data.limbo.LimboService; +import fr.xephi.authme.datasource.CacheDataSource; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.initialization.HasCleanup; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.SingletonStore; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap; + +/** + * Fetches various statistics, particularly regarding in-memory data that is stored. + */ +class DataStatistics implements DebugSection { + + @Inject + private PlayerCache playerCache; + + @Inject + private LimboService limboService; + + @Inject + private DataSource dataSource; + + @Inject + private SingletonStore singletonStore; + + @Override + public String getName() { + return "stats"; + } + + @Override + public String getDescription() { + return "Outputs general data statistics"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + sender.sendMessage("LimboPlayers in memory: " + applyToLimboPlayersMap(limboService, Map::size)); + sender.sendMessage("PlayerCache size: " + playerCache.getLogged() + " (= logged in players)"); + + outputDatabaseStats(sender); + outputInjectorStats(sender); + } + + private void outputDatabaseStats(CommandSender sender) { + sender.sendMessage("Total players in DB: " + dataSource.getAccountsRegistered()); + sender.sendMessage("Total marked as logged in in DB: " + dataSource.getLoggedPlayers().size()); + if (dataSource instanceof CacheDataSource) { + CacheDataSource cacheDataSource = (CacheDataSource) this.dataSource; + sender.sendMessage("Cached PlayerAuth objects: " + cacheDataSource.getCachedAuths().size()); + } + } + + private void outputInjectorStats(CommandSender sender) { + sender.sendMessage( + String.format("Singleton Java classes: %d (Reloadable: %d / SettingsDependent: %d / HasCleanup: %d)", + singletonStore.retrieveAllOfType().size(), + singletonStore.retrieveAllOfType(Reloadable.class).size(), + singletonStore.retrieveAllOfType(SettingsDependent.class).size(), + singletonStore.retrieveAllOfType(HasCleanup.class).size())); + } +} 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 5166df0da..5cd7fb4f5 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 @@ -19,9 +19,9 @@ public class DebugCommand implements ExecutableCommand { @Inject private Factory debugSectionFactory; - private Set> sectionClasses = ImmutableSet.of( - PermissionGroups.class, TestEmailSender.class, PlayerAuthViewer.class, LimboPlayerViewer.class, - CountryLookup.class); + private Set> sectionClasses = ImmutableSet.of(PermissionGroups.class, + DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, LimboPlayerViewer.class, CountryLookup.class, + HasPermissionChecker.class, TestEmailSender.class); private Map sections; diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java index e2861ca4f..78960ce88 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java @@ -1,15 +1,22 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.limbo.LimboService; import org.bukkit.Location; +import java.lang.reflect.Field; import java.math.RoundingMode; import java.text.DecimalFormat; +import java.util.Map; +import java.util.function.Function; /** * Utilities used within the DebugSection implementations. */ final class DebugSectionUtils { + private static Field limboEntriesField; + private DebugSectionUtils() { } @@ -52,4 +59,40 @@ final class DebugSectionUtils { df.setRoundingMode(RoundingMode.HALF_UP); return df.format(number); } + + private static Field getLimboPlayerEntriesField() { + if (limboEntriesField == null) { + try { + Field field = LimboService.class.getDeclaredField("entries"); + field.setAccessible(true); + limboEntriesField = field; + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService entries field:", e); + } + } + return limboEntriesField; + } + + /** + * Applies the given function to the map in LimboService containing the LimboPlayers. + * As we don't want to expose this information in non-debug settings, this is done with reflection. + * Exceptions are generously caught and {@code null} is returned on failure. + * + * @param limboService the limbo service instance to get the map from + * @param function the function to apply to the map + * @param the result type of the function + * + * @return player names for which there is a LimboPlayer (or error message upon failure) + */ + static U applyToLimboPlayersMap(LimboService limboService, Function function) { + Field limboPlayerEntriesField = getLimboPlayerEntriesField(); + if (limboPlayerEntriesField != null) { + try { + return function.apply((Map) limboEntriesField.get(limboService)); + } catch (Exception e) { + ConsoleLogger.logException("Could not retrieve LimboService values:", e); + } + } + return null; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java new file mode 100644 index 000000000..0df27bccb --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java @@ -0,0 +1,130 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.DefaultPermission; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.permission.PlayerPermission; +import fr.xephi.authme.permission.PlayerStatePermission; +import fr.xephi.authme.service.BukkitService; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + +/** + * Checks if a player has a given permission, as checked by AuthMe. + */ +class HasPermissionChecker implements DebugSection { + + static final List> PERMISSION_NODE_CLASSES = + Arrays.asList(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class); + + @Inject + private PermissionsManager permissionsManager; + + @Inject + private BukkitService bukkitService; + + @Override + public String getName() { + return "perm"; + } + + @Override + public String getDescription() { + return "Checks if player has given permission: /authme debug perm bobby my.perm"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.size() < 2) { + sender.sendMessage("Check if a player has permission:"); + sender.sendMessage("Example: /authme debug perm bobby my.perm.node"); + return; + } + + final String playerName = arguments.get(0); + final String permissionNode = arguments.get(1); + + Player player = bukkitService.getPlayerExact(playerName); + if (player == null) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); + if (offlinePlayer == null) { + sender.sendMessage(ChatColor.DARK_RED + "Player '" + playerName + "' does not exist"); + } else { + sender.sendMessage("Player '" + playerName + "' not online; checking with offline player"); + performPermissionCheck(offlinePlayer, permissionNode, permissionsManager::hasPermissionOffline, sender); + } + } else { + performPermissionCheck(player, permissionNode, permissionsManager::hasPermission, sender); + } + } + + /** + * Performs a permission check and informs the given sender of the result. {@code permissionChecker} is the + * permission check to perform with the given {@code node} and the {@code player}. + * + * @param player the player to check a permission for + * @param node the node of the permission to check + * @param permissionChecker permission checking function + * @param sender the sender to inform of the result + * @param

    the player type + */ + private static

    void performPermissionCheck( + P player, String node, BiFunction permissionChecker, CommandSender sender) { + + PermissionNode permNode = getPermissionNode(sender, node); + if (permissionChecker.apply(player, permNode)) { + sender.sendMessage(ChatColor.DARK_GREEN + "Success: player '" + player.getName() + + "' has permission '" + node + "'"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "Check failed: player '" + player.getName() + + "' does NOT have permission '" + node + "'"); + } + + } + + /** + * Based on the given permission node (String), tries to find the according AuthMe {@link PermissionNode} + * instance, or creates a new one if not available. + * + * @param sender the sender (used to inform him if no AuthMe PermissionNode can be matched) + * @param node the node to search for + * @return the node as {@link PermissionNode} object + */ + private static PermissionNode getPermissionNode(CommandSender sender, String node) { + Optional permNode = PERMISSION_NODE_CLASSES.stream() + .map(Class::getEnumConstants) + .flatMap(Arrays::stream) + .filter(perm -> perm.getNode().equals(node)) + .findFirst(); + if (permNode.isPresent()) { + return permNode.get(); + } else { + sender.sendMessage("Did not detect AuthMe permission; using default permission = DENIED"); + return createPermNode(node); + } + } + + private static PermissionNode createPermNode(String node) { + return new PermissionNode() { + @Override + public String getNode() { + return node; + } + + @Override + public DefaultPermission getDefaultPermission() { + return DefaultPermission.NOT_ALLOWED; + } + }; + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java index c93ad2391..bf7f9b3c7 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme.debug; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.data.limbo.persistence.LimboPersistence; @@ -10,15 +9,13 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import javax.inject.Inject; -import java.lang.reflect.Field; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap; /** * Shows the data stored in LimboPlayers and the equivalent properties on online players. @@ -34,8 +31,6 @@ class LimboPlayerViewer implements DebugSection { @Inject private BukkitService bukkitService; - private Field limboServiceEntries; - @Override public String getName() { return "limbo"; @@ -50,7 +45,7 @@ class LimboPlayerViewer implements DebugSection { public void execute(CommandSender sender, List arguments) { if (arguments.isEmpty()) { sender.sendMessage("/authme debug limbo : show a player's limbo info"); - sender.sendMessage("Available limbo records: " + getLimboKeys()); + sender.sendMessage("Available limbo records: " + applyToLimboPlayersMap(limboService, Map::keySet)); return; } @@ -73,35 +68,6 @@ class LimboPlayerViewer implements DebugSection { sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups"); } - /** - * Gets the names of the LimboPlayers in the LimboService. As we don't want to expose this - * information in non-debug settings, this is done over reflections. Since this is not a - * crucial feature, we generously catch all Exceptions - * - * @return player names for which there is a LimboPlayer (or error message upon failure) - */ - @SuppressWarnings("unchecked") - private Set getLimboKeys() { - // Lazy initialization - if (limboServiceEntries == null) { - try { - Field limboServiceEntries = LimboService.class.getDeclaredField("entries"); - limboServiceEntries.setAccessible(true); - this.limboServiceEntries = limboServiceEntries; - } catch (Exception e) { - ConsoleLogger.logException("Could not retrieve LimboService entries field:", e); - return Collections.singleton("Error retrieving LimboPlayer collection"); - } - } - - try { - return (Set) ((Map) limboServiceEntries.get(limboService)).keySet(); - } catch (Exception e) { - ConsoleLogger.logException("Could not retrieve LimboService values:", e); - return Collections.singleton("Error retrieving LimboPlayer values"); - } - } - /** * Displays the info for the given LimboPlayer and Player to the provided CommandSender. */ diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java index caf617883..e8443260d 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtilsTest.java @@ -1,11 +1,20 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.limbo.LimboPlayer; +import fr.xephi.authme.data.limbo.LimboService; import org.bukkit.Location; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; /** * Test for {@link DebugSectionUtils}. @@ -43,4 +52,18 @@ public class DebugSectionUtilsTest { public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(DebugSectionUtils.class); } + + @Test + public void shouldFetchMapInLimboService() { + // given + LimboService limboService = mock(LimboService.class); + Map limboMap = new HashMap<>(); + ReflectionTestUtils.setField(LimboService.class, limboService, "entries", limboMap); + + // when + Map map = DebugSectionUtils.applyToLimboPlayersMap(limboService, Function.identity()); + + // then + assertThat(map, sameInstance(limboMap)); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java new file mode 100644 index 000000000..9f7c6a92f --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java @@ -0,0 +1,97 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.ClassCollector; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.service.BukkitService; +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.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.containsString; +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.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link HasPermissionChecker}. + */ +@RunWith(MockitoJUnitRunner.class) +public class HasPermissionCheckerTest { + + @InjectMocks + private HasPermissionChecker hasPermissionChecker; + + @Mock + private PermissionsManager permissionsManager; + + @Mock + private BukkitService bukkitService; + + @Test + public void shouldListAllPermissionNodeClasses() { + // given + List> permissionClasses = + new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT) + .collectClasses(PermissionNode.class).stream() + .filter(clz -> !clz.isInterface()) + .collect(Collectors.toList()); + + // when / then + assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES.containsAll(permissionClasses), equalTo(true)); + assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES, hasSize(permissionClasses.size())); + } + + @Test + public void shouldShowUsageInfo() { + // given + CommandSender sender = mock(CommandSender.class); + + // when + hasPermissionChecker.execute(sender, emptyList()); + + // then + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, atLeast(2)).sendMessage(msgCaptor.capture()); + assertThat( + msgCaptor.getAllValues().stream().anyMatch(msg -> msg.contains("/authme debug perm bobby my.perm.node")), + equalTo(true)); + } + + @Test + public void shouldShowSuccessfulTestWithRegularPlayer() { + // given + String name = "Chuck"; + Player player = mock(Player.class); + given(bukkitService.getPlayerExact(name)).willReturn(player); + PermissionNode permission = AdminPermission.CHANGE_EMAIL; + given(permissionsManager.hasPermission(player, permission)).willReturn(true); + CommandSender sender = mock(CommandSender.class); + + // when + hasPermissionChecker.execute(sender, asList(name, permission.getNode())); + + // then + verify(bukkitService).getPlayerExact(name); + verify(permissionsManager).hasPermission(player, permission); + verify(sender).sendMessage(argThat(containsString("Success: player '" + player.getName() + + "' has permission '" + permission.getNode() + "'"))); + } +} From 4e54fa4a4e3ae45d898aee6bfb3742af50e417cd Mon Sep 17 00:00:00 2001 From: Gnat008 Date: Tue, 21 Mar 2017 17:38:53 -0400 Subject: [PATCH 105/125] Restrict changing password after recovery to the same username --- .../service/PasswordRecoveryService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java index 79e5beec2..8b9c3bfe6 100644 --- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -11,6 +11,7 @@ import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.util.expiring.Duration; +import fr.xephi.authme.util.expiring.ExpiringMap; import fr.xephi.authme.util.expiring.ExpiringSet; import org.bukkit.entity.Player; @@ -47,13 +48,13 @@ public class PasswordRecoveryService implements Reloadable { private Messages messages; private ExpiringSet emailCooldown; - private ExpiringSet successfulRecovers; + private ExpiringMap successfulRecovers; @PostConstruct private void initEmailCooldownSet() { emailCooldown = new ExpiringSet<>( commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); - successfulRecovers = new ExpiringSet<>( + successfulRecovers = new ExpiringMap<>( commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES); } @@ -103,7 +104,7 @@ public class PasswordRecoveryService implements Reloadable { String address = PlayerUtils.getPlayerIp(player); - successfulRecovers.add(address); + successfulRecovers.put(name, address); commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD); } else { commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); @@ -134,12 +135,15 @@ public class PasswordRecoveryService implements Reloadable { * @return True if the player can change their password. */ public boolean canChangePassword(Player player) { - String address = PlayerUtils.getPlayerIp(player); - Duration waitDuration = successfulRecovers.getExpiration(address); - if (waitDuration.getDuration() > 0) { - messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR); + String name = player.getName(); + String playerAddress = PlayerUtils.getPlayerIp(player); + String storedAddress = successfulRecovers.get(name); + + if (storedAddress == null || !playerAddress.equals(storedAddress)) { + messages.send(player, MessageKey.CHANGE_PASSWORD_EXPIRED); return false; } + return true; } @@ -147,5 +151,7 @@ public class PasswordRecoveryService implements Reloadable { public void reload() { emailCooldown.setExpiration( commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS); + successfulRecovers.setExpiration( + commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES); } } From 7dbf5551c913f2b83ca7b856826afcf6910c1d52 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 21 Mar 2017 22:59:21 +0100 Subject: [PATCH 106/125] Cleanup: avoid injecting Injector directly - Inject SingletonStore to restrict the possible functions - Refactor PasswordSecurityTest to correspond to the usual way of testing --- .../executable/authme/ReloadCommand.java | 15 +-- .../fr/xephi/authme/task/CleanupTask.java | 9 +- .../executable/authme/ReloadCommandTest.java | 35 +++---- .../authme/security/PasswordSecurityTest.java | 97 +++++++++---------- .../fr/xephi/authme/task/CleanupTaskTest.java | 8 +- .../EncryptionMethodInfoGatherer.java | 2 +- 6 files changed, 80 insertions(+), 86 deletions(-) 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 5a3604f1d..af0aaaca2 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 @@ -1,12 +1,12 @@ package fr.xephi.authme.command.executable.authme; -import ch.jalu.injector.Injector; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.settings.Settings; @@ -25,9 +25,6 @@ public class ReloadCommand implements ExecutableCommand { @Inject private AuthMe plugin; - @Inject - private Injector injector; - @Inject private Settings settings; @@ -37,6 +34,12 @@ public class ReloadCommand implements ExecutableCommand { @Inject private CommonService commonService; + @Inject + private SingletonStore reloadableStore; + + @Inject + private SingletonStore settingsDependentStore; + @Override public void executeCommand(CommandSender sender, List arguments) { try { @@ -56,10 +59,10 @@ public class ReloadCommand implements ExecutableCommand { } private void performReloadOnServices() { - injector.retrieveAllOfType(Reloadable.class) + reloadableStore.retrieveAllOfType() .forEach(r -> r.reload()); - injector.retrieveAllOfType(SettingsDependent.class) + settingsDependentStore.retrieveAllOfType() .forEach(s -> s.reload(settings)); } } diff --git a/src/main/java/fr/xephi/authme/task/CleanupTask.java b/src/main/java/fr/xephi/authme/task/CleanupTask.java index 1a5bbdd65..f373c3e58 100644 --- a/src/main/java/fr/xephi/authme/task/CleanupTask.java +++ b/src/main/java/fr/xephi/authme/task/CleanupTask.java @@ -1,7 +1,7 @@ package fr.xephi.authme.task; -import ch.jalu.injector.Injector; import fr.xephi.authme.initialization.HasCleanup; +import fr.xephi.authme.initialization.factory.SingletonStore; import org.bukkit.scheduler.BukkitRunnable; import javax.inject.Inject; @@ -12,15 +12,14 @@ import javax.inject.Inject; public class CleanupTask extends BukkitRunnable { @Inject - private Injector injector; + private SingletonStore hasCleanupStore; CleanupTask() { } @Override public void run() { - for (HasCleanup service : injector.retrieveAllOfType(HasCleanup.class)) { - service.performCleanup(); - } + hasCleanupStore.retrieveAllOfType() + .forEach(HasCleanup::performCleanup); } } diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java index 3d9cf354e..ad49600f5 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java @@ -1,12 +1,12 @@ package fr.xephi.authme.command.executable.authme; -import ch.jalu.injector.Injector; import fr.xephi.authme.AuthMe; import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.SettingsDependent; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.output.LogLevel; import fr.xephi.authme.service.CommonService; @@ -23,17 +23,14 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.hamcrest.MockitoHamcrest.argThat; @@ -49,9 +46,6 @@ public class ReloadCommandTest { @Mock private AuthMe authMe; - @Mock - private Injector injector; - @Mock private Settings settings; @@ -61,6 +55,12 @@ public class ReloadCommandTest { @Mock private CommonService commandService; + @Mock + private SingletonStore reloadableStore; + + @Mock + private SingletonStore settingsDependentStore; + @BeforeClass public static void setUpLogger() { TestHelper.setupLogger(); @@ -83,11 +83,11 @@ public class ReloadCommandTest { mock(Reloadable.class), mock(Reloadable.class), mock(Reloadable.class)); List dependents = Arrays.asList( mock(SettingsDependent.class), mock(SettingsDependent.class)); - given(injector.retrieveAllOfType(Reloadable.class)).willReturn(reloadables); - given(injector.retrieveAllOfType(SettingsDependent.class)).willReturn(dependents); + given(reloadableStore.retrieveAllOfType()).willReturn(reloadables); + given(settingsDependentStore.retrieveAllOfType()).willReturn(dependents); // when - command.executeCommand(sender, Collections.emptyList()); + command.executeCommand(sender, Collections.emptyList()); // then verify(settings).reload(); @@ -99,16 +99,16 @@ public class ReloadCommandTest { public void shouldHandleReloadError() { // given CommandSender sender = mock(CommandSender.class); - doThrow(IllegalStateException.class).when(injector).retrieveAllOfType(Reloadable.class); + doThrow(IllegalStateException.class).when(reloadableStore).retrieveAllOfType(); given(settings.getProperty(DatabaseSettings.BACKEND)).willReturn(DataSourceType.MYSQL); given(dataSource.getType()).willReturn(DataSourceType.MYSQL); // when - command.executeCommand(sender, Collections.emptyList()); + command.executeCommand(sender, Collections.emptyList()); // then verify(settings).reload(); - verify(injector).retrieveAllOfType(Reloadable.class); + verify(reloadableStore).retrieveAllOfType(); verify(sender).sendMessage(argThat(containsString("Error occurred"))); verify(authMe).stopOrUnload(); } @@ -120,15 +120,16 @@ public class ReloadCommandTest { CommandSender sender = mock(CommandSender.class); given(settings.getProperty(DatabaseSettings.BACKEND)).willReturn(DataSourceType.MYSQL); given(dataSource.getType()).willReturn(DataSourceType.SQLITE); - given(injector.retrieveAllOfType(Reloadable.class)).willReturn(new ArrayList()); - given(injector.retrieveAllOfType(SettingsDependent.class)).willReturn(new ArrayList()); + given(reloadableStore.retrieveAllOfType()).willReturn(Collections.emptyList()); + given(settingsDependentStore.retrieveAllOfType()).willReturn(Collections.emptyList()); // when - command.executeCommand(sender, Collections.emptyList()); + command.executeCommand(sender, Collections.emptyList()); // then verify(settings).reload(); - verify(injector, times(2)).retrieveAllOfType(any(Class.class)); + verify(reloadableStore).retrieveAllOfType(); + verify(settingsDependentStore).retrieveAllOfType(); verify(sender).sendMessage(argThat(containsString("cannot change database type"))); } diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index cebde5da6..6bd0b45ad 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -2,11 +2,14 @@ package fr.xephi.authme.security; import ch.jalu.injector.Injector; import ch.jalu.injector.InjectorBuilder; +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.TestHelper; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; -import fr.xephi.authme.initialization.factory.FactoryDependencyHandler; +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.security.crypts.Joomla; @@ -15,14 +18,12 @@ import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.event.Event; import org.bukkit.plugin.PluginManager; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; -import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import java.util.Collections; @@ -44,10 +45,11 @@ import static org.mockito.hamcrest.MockitoHamcrest.argThat; /** * Test for {@link PasswordSecurity}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(DelayedInjectionRunner.class) public class PasswordSecurityTest { - private Injector injector; + @InjectDelayed + private PasswordSecurity passwordSecurity; @Mock private Settings settings; @@ -58,6 +60,9 @@ public class PasswordSecurityTest { @Mock private DataSource dataSource; + @Mock + private Factory hashAlgorithmFactory; + @Mock private EncryptionMethod method; @@ -68,7 +73,7 @@ public class PasswordSecurityTest { TestHelper.setupLogger(); } - @Before + @BeforeInjecting public void setUpMocks() { caughtClassInEvent = null; @@ -85,12 +90,24 @@ public class PasswordSecurityTest { return null; } }).when(pluginManager).callEvent(any(Event.class)); - injector = new InjectorBuilder() - .addHandlers(new FactoryDependencyHandler()) - .addDefaultHandlers("fr.xephi.authme").create(); + + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.BCRYPT); + given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(Collections.emptySet()); + given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); + + Injector injector = new InjectorBuilder() + .addDefaultHandlers("fr.xephi.authme.security.crypts") + .create(); injector.register(Settings.class, settings); - injector.register(DataSource.class, dataSource); - injector.register(PluginManager.class, pluginManager); + + given(hashAlgorithmFactory.newInstance(any(Class.class))).willAnswer(invocation -> { + Object o = injector.createIfHasDependencies(invocation.getArgument(0)); + if (o == null) { + throw new IllegalArgumentException("Cannot create object of class '" + invocation.getArgument(0) + + "': missing class that needs to be provided?"); + } + return o; + }); } @Test @@ -104,11 +121,9 @@ public class PasswordSecurityTest { given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(true); - initSettings(HashAlgorithm.BCRYPT); - PasswordSecurity security = newPasswordSecurity(); // when - boolean result = security.comparePassword(clearTextPass, playerName); + boolean result = passwordSecurity.comparePassword(clearTextPass, playerName); // then assertThat(result, equalTo(true)); @@ -127,11 +142,9 @@ public class PasswordSecurityTest { given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); - initSettings(HashAlgorithm.CUSTOM); - PasswordSecurity security = newPasswordSecurity(); // when - boolean result = security.comparePassword(clearTextPass, playerName); + boolean result = passwordSecurity.comparePassword(clearTextPass, playerName); // then assertThat(result, equalTo(false)); @@ -145,13 +158,10 @@ public class PasswordSecurityTest { // given String playerName = "bobby"; String clearTextPass = "tables"; - given(dataSource.getPassword(playerName)).willReturn(null); - initSettings(HashAlgorithm.MD5); - PasswordSecurity security = newPasswordSecurity(); // when - boolean result = security.comparePassword(clearTextPass, playerName); + boolean result = passwordSecurity.comparePassword(clearTextPass, playerName); // then assertThat(result, equalTo(false)); @@ -175,12 +185,12 @@ public class PasswordSecurityTest { given(dataSource.getPassword(argThat(equalToIgnoringCase(playerName)))).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); given(method.computeHash(clearTextPass, playerLowerCase)).willReturn(newPassword); - initSettings(HashAlgorithm.MD5); + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5); given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(newHashSet(HashAlgorithm.BCRYPT)); - PasswordSecurity security = newPasswordSecurity(); + passwordSecurity.reload(); // when - boolean result = security.comparePassword(clearTextPass, playerName); + boolean result = passwordSecurity.comparePassword(clearTextPass, playerName); // then assertThat(result, equalTo(true)); @@ -201,11 +211,13 @@ public class PasswordSecurityTest { String clearTextPass = "someInvalidPassword"; given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); - initSettings(HashAlgorithm.MD5); - PasswordSecurity security = newPasswordSecurity(); + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5); + given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn( + newHashSet(HashAlgorithm.DOUBLEMD5, HashAlgorithm.JOOMLA, HashAlgorithm.SMF, HashAlgorithm.SHA256)); + passwordSecurity.reload(); // when - boolean result = security.comparePassword(clearTextPass, playerName); + boolean result = passwordSecurity.comparePassword(clearTextPass, playerName); // then assertThat(result, equalTo(false)); @@ -220,11 +232,11 @@ public class PasswordSecurityTest { String usernameLowerCase = username.toLowerCase(); HashedPassword hashedPassword = new HashedPassword("$T$est#Hash", "__someSalt__"); given(method.computeHash(password, usernameLowerCase)).willReturn(hashedPassword); - initSettings(HashAlgorithm.JOOMLA); - PasswordSecurity security = newPasswordSecurity(); + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.JOOMLA); + passwordSecurity.reload(); // when - HashedPassword result = security.computeHash(password, username); + HashedPassword result = passwordSecurity.computeHash(password, username); // then assertThat(result, equalTo(hashedPassword)); @@ -242,11 +254,11 @@ public class PasswordSecurityTest { String username = "someone12"; HashedPassword hashedPassword = new HashedPassword("~T!est#Hash"); given(method.hasSeparateSalt()).willReturn(true); - initSettings(HashAlgorithm.XAUTH); - PasswordSecurity security = newPasswordSecurity(); + given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.XAUTH); + passwordSecurity.reload(); // when - boolean result = security.comparePassword(password, hashedPassword, username); + boolean result = passwordSecurity.comparePassword(password, hashedPassword, username); // then assertThat(result, equalTo(false)); @@ -258,8 +270,6 @@ public class PasswordSecurityTest { @Test public void shouldReloadSettings() { // given - initSettings(HashAlgorithm.BCRYPT); - PasswordSecurity passwordSecurity = newPasswordSecurity(); given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(HashAlgorithm.MD5); given(settings.getProperty(SecuritySettings.LEGACY_HASHES)) .willReturn(newHashSet(HashAlgorithm.CUSTOM, HashAlgorithm.BCRYPT)); @@ -274,21 +284,4 @@ public class PasswordSecurityTest { assertThat(ReflectionTestUtils.getFieldValue(PasswordSecurity.class, passwordSecurity, "legacyAlgorithms"), equalTo(legacyHashesSet)); } - - private PasswordSecurity newPasswordSecurity() { - // Use this method to make sure we have all dependents of PasswordSecurity already registered as mocks - PasswordSecurity passwordSecurity = injector.createIfHasDependencies(PasswordSecurity.class); - if (passwordSecurity == null) { - throw new IllegalStateException("Cannot create PasswordSecurity directly! " - + "Did you forget to provide a dependency as mock?"); - } - return passwordSecurity; - } - - private void initSettings(HashAlgorithm algorithm) { - given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(algorithm); - given(settings.getProperty(SecuritySettings.LEGACY_HASHES)).willReturn(Collections.emptySet()); - given(settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND)).willReturn(8); - } - } diff --git a/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java b/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java index c58e8276c..33cd360be 100644 --- a/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java +++ b/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java @@ -1,7 +1,7 @@ package fr.xephi.authme.task; -import ch.jalu.injector.Injector; import fr.xephi.authme.initialization.HasCleanup; +import fr.xephi.authme.initialization.factory.SingletonStore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -13,7 +13,6 @@ import java.util.List; import static java.util.Arrays.asList; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; /** @@ -26,13 +25,13 @@ public class CleanupTaskTest { private CleanupTask cleanupTask; @Mock - private Injector injector; + private SingletonStore hasCleanupStore; @Test public void shouldPerformCleanup() { // given List services = asList(mock(HasCleanup.class), mock(HasCleanup.class), mock(HasCleanup.class)); - given(injector.retrieveAllOfType(HasCleanup.class)).willReturn(services); + given(hasCleanupStore.retrieveAllOfType()).willReturn(services); // when cleanupTask.run(); @@ -41,6 +40,5 @@ public class CleanupTaskTest { verify(services.get(0)).performCleanup(); verify(services.get(1)).performCleanup(); verify(services.get(2)).performCleanup(); - verify(injector, only()).retrieveAllOfType(HasCleanup.class); } } diff --git a/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java b/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java index e327e4847..0a1d3fa59 100644 --- a/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java +++ b/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java @@ -57,7 +57,7 @@ public class EncryptionMethodInfoGatherer { private static MethodDescription createDescription(HashAlgorithm algorithm) { Class clazz = algorithm.getClazz(); - EncryptionMethod method = injector.newInstance(clazz); + EncryptionMethod method = injector.createIfHasDependencies(clazz); if (method == null) { throw new NullPointerException("Method for '" + algorithm + "' is null"); } From da8b33f1bc2bd4d7f9acbbe24efaf6d2bff37192 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 22 Mar 2017 08:55:18 +0100 Subject: [PATCH 107/125] Make Travis update JDK 8 version cf. https://github.com/travis-ci/travis-ci/issues/3259 --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index bca4843fd..ba8dd103d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ sudo: false +addons: + apt: + packages: + - oracle-java8-installer language: java jdk: oraclejdk8 From 4053a0e3281c56062157be2eff08c2b1075e894e Mon Sep 17 00:00:00 2001 From: EbonJaguar Date: Wed, 22 Mar 2017 17:11:18 -0400 Subject: [PATCH 108/125] If recovery code is required, dont send new password --- .../executable/email/ProcessCodeCommand.java | 15 +----- .../executable/email/RecoverEmailCommand.java | 3 +- .../fr/xephi/authme/message/MessageKey.java | 3 ++ .../service/PasswordRecoveryService.java | 19 ++++++-- src/main/resources/messages/messages_bg.yml | 6 +-- src/main/resources/messages/messages_br.yml | 1 + src/main/resources/messages/messages_cz.yml | 1 + src/main/resources/messages/messages_de.yml | 1 + src/main/resources/messages/messages_en.yml | 1 + src/main/resources/messages/messages_es.yml | 1 + src/main/resources/messages/messages_eu.yml | 1 + src/main/resources/messages/messages_fi.yml | 1 + src/main/resources/messages/messages_fr.yml | 1 + src/main/resources/messages/messages_gl.yml | 1 + src/main/resources/messages/messages_hu.yml | 1 + src/main/resources/messages/messages_id.yml | 1 + src/main/resources/messages/messages_it.yml | 1 + src/main/resources/messages/messages_ko.yml | 1 + src/main/resources/messages/messages_lt.yml | 1 + src/main/resources/messages/messages_nl.yml | 1 + src/main/resources/messages/messages_pl.yml | 1 + src/main/resources/messages/messages_pt.yml | 2 + src/main/resources/messages/messages_ro.yml | 1 + src/main/resources/messages/messages_ru.yml | 1 + src/main/resources/messages/messages_sk.yml | 1 + src/main/resources/messages/messages_tr.yml | 1 + src/main/resources/messages/messages_uk.yml | 1 + src/main/resources/messages/messages_vn.yml | 1 + src/main/resources/messages/messages_zhcn.yml | 1 + src/main/resources/messages/messages_zhhk.yml | 1 + src/main/resources/messages/messages_zhmc.yml | 1 + src/main/resources/messages/messages_zhtw.yml | 1 + .../email/ProcessCodeCommandTest.java | 48 +++---------------- 33 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java index 8a7bbf3f1..0883c18f9 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java @@ -1,8 +1,6 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.command.PlayerCommand; -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PasswordRecoveryService; @@ -20,9 +18,6 @@ public class ProcessCodeCommand extends PlayerCommand { @Inject private CommonService commonService; - @Inject - private DataSource dataSource; - @Inject private RecoveryCodeService codeService; @@ -36,14 +31,8 @@ public class ProcessCodeCommand extends PlayerCommand { if (codeService.hasTriesLeft(name)) { if (codeService.isCodeValid(name, code)) { - PlayerAuth auth = dataSource.getAuth(name); - String email = auth.getEmail(); - if (email == null || "your@email.com".equalsIgnoreCase(email)) { - commonService.send(player, MessageKey.INVALID_EMAIL); - return; - } - - recoveryService.generateAndSendNewPassword(player, email); + commonService.send(player, MessageKey.RECOVERY_CODE_CORRECT); + recoveryService.addSuccessfulRecovery(player); codeService.removeCode(name); } else { commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE, 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 455fc6134..142a4b600 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 @@ -66,9 +66,10 @@ public class RecoverEmailCommand extends PlayerCommand { } if (recoveryCodeService.isRecoveryCodeNeeded()) { - // Process /email recovery addr@example.com + // Recovery code is needed; generate and send one recoveryService.createAndSendRecoveryCode(player, email); } else { + // Code not needed, just send them a new password recoveryService.generateAndSendNewPassword(player, email); } } diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index 961ec1949..a54423862 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -230,6 +230,9 @@ public enum MessageKey { /** You have exceeded the maximum number of attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one. */ RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"), + /** Recovery code entered correctly! */ + RECOVERY_CODE_CORRECT("recovery_code_correct"), + /** Please use the command /email setpassword to change your password immediately. */ RECOVERY_CHANGE_PASSWORD("recovery_change_password"), diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java index 8b9c3bfe6..301d5cb3b 100644 --- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -101,16 +101,25 @@ public class PasswordRecoveryService implements Reloadable { if (couldSendMail) { commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); emailCooldown.add(player.getName().toLowerCase()); - - String address = PlayerUtils.getPlayerIp(player); - - successfulRecovers.put(name, address); - commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD); } else { commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); } } + /** + * Allows a player to change their password after + * correctly entering a recovery code. + * + * @param player The player recovering their password. + */ + public void addSuccessfulRecovery(Player player) { + String name = player.getName(); + String address = PlayerUtils.getPlayerIp(player); + + successfulRecovers.put(name, address); + commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD); + } + /** * Check if a player is able to have emails sent. * diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 2e004b624..1ea7196ca 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -44,12 +44,12 @@ accounts_owned_self: 'Претежаваш %count акаунт/а:' accounts_owned_other: 'Потребителят %name има %count акаунт/а:' two_factor_create: '&2Кода е %code. Можеш да го провериш оттука: %url' recovery_code_sent: 'Възстановяващият код беше изпратен на твоят email адрес.' +# TODO: Missing tags %count recovery_code_incorrect: 'Възстановяващият код е неправилен! Използвайте: /email recovery имейл, за да генерирате нов' -vb_nonActiv: '&cТвоят акаунт все още не е актириван, моля провете своят email адрес!' -unregistered: '&cУспешно от-регистриран!' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' -vb_nonActiv: '&fТвоята регистрация не е активирана, моля провери своя Имейл!' +vb_nonActiv: '&cТвоят акаунт все още не е актириван, моля провете своят email адрес!' usage_unreg: '&cКоманда: /unregister парола' pwd_changed: '&2Паротала е променена успешно!' logged_in: '&cВече си вписан!' diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index 823c1cd84..b9801f949 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -51,6 +51,7 @@ recovery_code_sent: 'Um código de recuperação para redefinir sua senha foi en # TODO: Missing tags %count recovery_code_incorrect: 'O código de recuperação esta incorreto! Use /email recovery [email] para gerar um novo!' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cA sua conta ainda não está ativada, por favor, verifique seus e-mails!' usage_unreg: '&cUse: /unregister ' diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 0e6503fa3..55633d5f0 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Kód pro obnovení hesla byl odeslán na váš email.' # TODO: Missing tags %count recovery_code_incorrect: 'Kód pro není správný! Použijte příkaz /email recovery [email] pro vygenerování nového.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cTvůj účet není aktivovaný, zkontroluj si svůj E-mail.' usage_unreg: '&cPoužij: "/unregister TvojeHeslo".' diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index 65b3f79c2..ca2bc2fde 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Ein Wiederherstellungscode zum Zurücksetzen deines Passwor # TODO: Missing tags %count recovery_code_incorrect: 'Der Wiederherstellungscode stimmt nicht! Nutze /email recovery [email] um einen neuen zu generieren.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cDein Account wurde noch nicht aktiviert. Bitte prüfe deine E-Mails!' usage_unreg: '&cBenutze: /unregister ' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index d1f83d2ce..b6f6ec6a8 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -46,6 +46,7 @@ two_factor_create: '&2Your secret code is %code. You can scan it from here %url' recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' recovery_code_incorrect: 'The recovery code is not correct! You have %count tries remaining.' recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +recovery_code_correct: 'Recovery code entered correctly!' recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!' usage_unreg: '&cUsage: /unregister ' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index 808b0cce8..d2c322dd4 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -50,6 +50,7 @@ recovery_code_sent: 'El código de recuperación para recuperar tu contraseña s # TODO: Missing tags %count recovery_code_incorrect: '¡El código de recuperación no es correcto! Usa "/email recovery [email]" para generar uno nuevo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fTu cuenta no está activada aún, ¡revisa tu correo!' usage_unreg: '&cUso: /unregister contraseña' diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 0e9e9f8b4..098c16aab 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -47,6 +47,7 @@ unregistered: '&cZure erregistroa ezabatu duzu!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fZure kontua aktibatu gabe dago, konfirmatu zure emaila!' usage_unreg: '&cErabili: /unregister password' diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index 81861b3b5..bf266218d 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -47,6 +47,7 @@ unregistered: '&cPelaajatili poistettu onnistuneesti!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fKäyttäjäsi ei ole vahvistettu!' usage_unreg: '&cKäyttötapa: /unregister password' diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 0c456dcc2..453955ff3 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -52,6 +52,7 @@ recovery_code_sent: 'Un code de récupération a été envoyé à votre adresse # TODO: Missing tags %count recovery_code_incorrect: '&cLe code de réinitialisation est incorrect!%nl%Faites "/email recovery [email]" pour en générer un nouveau.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fCe compte n''est pas actif, consultez vos emails !' usage_unreg: '&cPour supprimer votre compte, utilisez "/unregister "' diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index 235780fcc..b7575a98f 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -47,6 +47,7 @@ unregistered: '&cFeito! Xa non estás rexistrado!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fA túa conta aínda non está activada, comproba a túa bandexa de correo!!' usage_unreg: '&cUso: /unregister ' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index de9677ef9..5a6f5df2c 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'A jelszavad visszaállításához szükséges kódot sikere # TODO: Missing tags %count recovery_code_incorrect: 'A visszaállító kód helytelen volt! Használd a következő parancsot: /email recovery [email címed] egy új generálásához' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cA felhasználód aktiválása még nem történt meg, ellenőrizd a megadott emailed!' usage_unreg: '&cHasználat: "/unregister "' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index da720ad3f..ec289d8fe 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -47,6 +47,7 @@ unregistered: '&cUnregister berhasil!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cAkunmu belum diaktifkan, silahkan periksa email kamu!' # TODO usage_unreg: '&cUsage: /unregister ' diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 2e8f95019..0f03ca65d 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -49,6 +49,7 @@ recovery_code_sent: 'Una email contenente il codice di recupero per reimpostare # TODO: Missing tags %count recovery_code_incorrect: 'Il codice di recupero inserito non è corretto! Scrivi "/email recovery " per generarne uno nuovo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cIl tuo account non è stato ancora verificato, controlla fra le tue email per scoprire come attivarlo!' usage_unreg: '&cUtilizzo: /unregister ' diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index 25f369d0b..98f493b79 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -51,6 +51,7 @@ unregistered: '&c성공적으로 탈퇴했습니다!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&f당신의 계정은 아직 활성화되어있지 않습니다, 당신의 이메일을 확인해보세요!' usage_unreg: '&c사용법: /unregister 비밀번호' diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index b6da0c76c..324fc29a2 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -47,6 +47,7 @@ unregistered: '&aSekmingai issiregistravote!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&aJusu vartotojas nera patvirtintas, patikrinkite el.pasta.' usage_unreg: '&ePanaikinti registracija: "/unregister slaptazodis"' diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 3603a7f34..3ca9d325c 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Een herstelcode voor je wachtwoord is naar je mailbox gestu # TODO: Missing tags %count recovery_code_incorrect: 'De herstelcode is niet correct! Gebruik "/email recovery [email]" om een nieuwe te krijgen' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: 'Je account is nog niet geactiveerd, controleer je mailbox!' usage_unreg: '&cGebruik: /unregister password' diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 0b29216a2..9530ae06b 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -48,6 +48,7 @@ recovery_code_sent: 'Kod odzyskiwania hasla zostal wyslany na adres email przypi # TODO: Missing tags %count recovery_code_incorrect: 'Kod odzyskiwania hasla jest bledny! Uzyj /email recovery [email] aby wygenerowac nowy.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fTwoje konto nie zostalo aktywowane! Sprawdz maila.' usage_unreg: '&cUzycie: /unregister haslo' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 8b080da56..4e9d56232 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -45,8 +45,10 @@ 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' recovery_code_sent: 'O codigo para redefinir a senha foi enviado para o seu e-mail.' +# TODO: Missing tags %count recovery_code_incorrect: 'O codigo de recuperação está incorreto! Use "/email recovery [email]" para gerar um novo' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' 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 ' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index 9a82c4d18..e87a68de4 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Un cod de recuperare a parolei a fost trimis catre email-ul # TODO: Missing tags %count recovery_code_incorrect: 'Codul de recuperare nu este corect! Foloseste /email recovery [email] pentru a genera unul nou.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cContul tau nu este activat, te rugam verifica-ti email-ul!' usage_unreg: '&cFoloseste comanda: /unregister ' diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 22ac61870..56cca9748 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Код восстановления для сброса п # TODO: Missing tags %count recovery_code_incorrect: 'Код восстановления неверный! Введите /email recovery <Ваш Email>, чтобы отправить новый код' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&6Ваш аккаунт еще не активирован! Проверьте вашу почту!' usage_unreg: '&cИспользование: &e/unregister <Пароль>' diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index c0113afb5..47ae3b5c1 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -51,6 +51,7 @@ unregistered: '&cUcet bol vymazany!' # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&fUcet nie je aktivny. Prezri si svoj e-mail!' usage_unreg: '&cPríkaz: /unregister heslo' diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index b8195283c..6f9500db3 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Sifre sifirlama kodu eposta adresinize gonderildi.' # TODO: Missing tags %count recovery_code_incorrect: 'Kod dogru degil! Kullanim "/email recovery [eposta]" ile yeni bir kod olustur' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cHeabiniz henuz aktif edilmemis, e-postanizi kontrol edin!' usage_unreg: '&cKullanim: /unregister ' diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index c2aee2a41..c14477011 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -46,6 +46,7 @@ two_factor_create: '&2Ваш секретний код — %code %nl%&2Може # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cВаш акаунт ще не активовано. Будь ласка, провірте свою електронну пошту!' usage_unreg: '&cСинтаксис: /unregister <пароль>' diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index fb5a88726..2e397448d 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -47,6 +47,7 @@ recovery_code_sent: 'Một mã khôi phục mật khẩu đã được gửi đ # TODO: Missing tags %count recovery_code_incorrect: 'Mã khôi phục không đúng! Dùng lệnh /email recovery [email] để tạo một mã mới' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&cTài khoản của bạn chưa được kích hoạt, vui lòng kiểm tra email!' usage_unreg: '&cSử dụng: /unregister ' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index c894d39bb..19a14d347 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -48,6 +48,7 @@ recovery_code_sent: '一个用于重置您的密码的验证码已发到您的 # TODO: Missing tags %count recovery_code_incorrect: '验证码不正确! 使用 /email recovery [email] 以生成新的验证码' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&8[&6玩家系统&8] &f你的帐号还未激活,请查看你的邮箱!' usage_unreg: '&8[&6玩家系统&8] &c正确用法:“/unregister <密码>”' diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 684e1cc02..342893e34 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -51,6 +51,7 @@ two_factor_create: '&8[&6用戶系統 - 兩步驗證碼&8] &b你的登入金鑰 # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&8[&6用戶系統&8] &f你的帳戶還沒有經過電郵驗證 !' usage_unreg: '&8[&6用戶系統&8] &f用法: 《 /unregister <密碼> 》' diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index c443c38d7..25b29a87c 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -47,6 +47,7 @@ recovery_code_sent: '已將重設密碼的恢復代碼發送到您的電子郵 # TODO: Missing tags %count recovery_code_incorrect: '恢復代碼錯誤!使用指令: "/email recovery [電郵地址]" 生成新的一個恢復代碼。' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&c你的帳戶未激活,請確認電郵!' usage_unreg: '&c使用方法: "/unregister <你的密碼>"' diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index 6babd62c4..0d40b92da 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -51,6 +51,7 @@ two_factor_create: '&b【AuthMe - 兩步驗證碼】&b你的登入金鑰為&9「 # 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! You have %count tries remaining.' # TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' +# TODO recovery_code_correct: 'Recovery code entered correctly!' # TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' vb_nonActiv: '&b【AuthMe】&6你的帳號還沒有經過驗證! 檢查看看你的電子信箱 (Email) 吧!' usage_unreg: '&b【AuthMe】&6用法: &c"/unregister <密碼>"' diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java index 076839ee7..b5790cc44 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/ProcessCodeCommandTest.java @@ -1,7 +1,5 @@ package fr.xephi.authme.command.executable.email; -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PasswordRecoveryService; @@ -32,17 +30,12 @@ public class ProcessCodeCommandTest { @Mock private CommonService commonService; - @Mock - private DataSource dataSource; - @Mock private RecoveryCodeService codeService; @Mock private PasswordRecoveryService recoveryService; - private static final String DEFAULT_EMAIL = "your@email.com"; - @Test public void shouldSendErrorForInvalidRecoveryCode() { // given @@ -79,48 +72,21 @@ public class ProcessCodeCommandTest { } @Test - public void shouldHandleDefaultEmail() { + public void shouldProcessCorrectCode() { // given - String name = "Tract0r"; + String name = "Dwight"; + String code = "chickenDinner"; Player sender = mock(Player.class); given(sender.getName()).willReturn(name); - given(dataSource.getAuth(name)).willReturn(newAuthWithEmail(DEFAULT_EMAIL)); given(codeService.hasTriesLeft(name)).willReturn(true); - given(codeService.isCodeValid(name, "actual")).willReturn(true); + given(codeService.isCodeValid(name, code)).willReturn(true); // when - command.executeCommand(sender, Collections.singletonList("actual")); + command.runCommand(sender, Collections.singletonList(code)); // then - verify(dataSource).getAuth(name); - verifyNoMoreInteractions(dataSource); - verify(commonService).send(sender, MessageKey.INVALID_EMAIL); - } - - @Test - public void shouldGenerateAndSendPassword() { - // given - String name = "GenericName"; - Player sender = mock(Player.class); - given(sender.getName()).willReturn(name); - String email = "ran-out@example.com"; - PlayerAuth auth = newAuthWithEmail(email); - given(dataSource.getAuth(name)).willReturn(auth); - given(codeService.hasTriesLeft(name)).willReturn(true); - given(codeService.isCodeValid(name, "actual")).willReturn(true); - - // when - command.executeCommand(sender, Collections.singletonList("actual")); - - // then - verify(recoveryService).generateAndSendNewPassword(sender, email); + verify(commonService).send(sender, MessageKey.RECOVERY_CODE_CORRECT); + verify(recoveryService).addSuccessfulRecovery(sender); verify(codeService).removeCode(name); } - - private static PlayerAuth newAuthWithEmail(String email) { - return PlayerAuth.builder() - .name("name") - .email(email) - .build(); - } } From ce40b3798b6c7751035499c7368d907e5dfe67ee Mon Sep 17 00:00:00 2001 From: EbonJaguar Date: Wed, 22 Mar 2017 17:54:30 -0400 Subject: [PATCH 109/125] Add logging for when a player changes their password or has a new one generated --- .../authme/command/executable/email/SetPasswordCommand.java | 2 ++ .../java/fr/xephi/authme/service/PasswordRecoveryService.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java index eafea6df3..d5d084aa6 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.email; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.message.MessageKey; @@ -44,6 +45,7 @@ public class SetPasswordCommand extends PlayerCommand { if (!result.hasError()) { HashedPassword hashedPassword = passwordSecurity.computeHash(password, name); dataSource.updatePassword(name, hashedPassword); + ConsoleLogger.info("Player '" + name + "' has changed their password from recovery"); commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); } else { commonService.send(player, result.getMessageKey(), result.getArgs()); diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java index 301d5cb3b..befc1149a 100644 --- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -1,5 +1,6 @@ package fr.xephi.authme.service; +import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.mail.EmailService; @@ -96,6 +97,8 @@ public class PasswordRecoveryService implements Reloadable { String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH)); HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); + ConsoleLogger.info("Generating new password for '" + name + "'"); + dataSource.updatePassword(name, hashNew); boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass); if (couldSendMail) { From e77828b2287f76f66831d5dfe0a87b71c079b821 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 22 Mar 2017 23:14:02 +0100 Subject: [PATCH 110/125] Update docs / update recovery code command in email message - Update docs to reflect new commands, configurations (account recovery, limbo handling) and messages - Change message for the recovery code email to contain the new command --- docs/commands.md | 12 +++- docs/config.md | 34 +++++++++-- docs/translations.md | 58 +++++++++---------- .../authme/command/CommandInitializer.java | 10 ++-- .../service/PasswordRecoveryService.java | 5 +- src/main/resources/recovery_code_email.html | 2 +- 6 files changed, 75 insertions(+), 46 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index b9f68054e..344b643de 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,5 @@ - + ## AuthMe Commands You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >` @@ -47,6 +47,8 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
    Requires `authme.admin.converter` - **/authme messages**: Adds missing messages to the current messages file.
    Requires `authme.admin.updatemessages` +- **/authme debug** [child] [params]: Allows various operations for debugging. +
    Requires `authme.debug` - **/authme help** [query]: View detailed help for /authme commands. - **/login** <password>: Command to log in using AuthMeReloaded.
    Requires `authme.player.login` @@ -69,7 +71,11 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
    Requires `authme.player.email.add` - **/email change** <oldEmail> <newEmail>: Change an email address of your account.
    Requires `authme.player.email.change` -- **/email recover** <email> [code]: Recover your account using an Email address by sending a mail containing a new password. +- **/email recover** <email>: Recover your account using an Email address by sending a mail containing a new password. +
    Requires `authme.player.email.recover` +- **/email code** <code>: Recover your account by submitting a code delivered to your email. +
    Requires `authme.player.email.recover` +- **/email setpassword** <password>: Set a new password after successfully recovering your account.
    Requires `authme.player.email.recover` - **/email help** [query]: View detailed help for /email commands. - **/captcha** <captcha>: Captcha command for AuthMeReloaded. @@ -78,4 +84,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`). --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 23 18:25:12 CEST 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:32 CET 2017 diff --git a/docs/config.md b/docs/config.md index 8216aafd5..13e704e7a 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, @@ -10,7 +10,7 @@ the generated config.yml file. DataSource: # What type of database do you want to use? - # Valid values: sqlite, mysql + # Valid values: SQLITE, MYSQL backend: 'SQLITE' # Enable database caching, should improve database performance caching: true @@ -71,6 +71,12 @@ ExternalBoardOptions: phpbbTablePrefix: 'phpbb_' # phpBB activated group ID; 2 is the default registered group defined by phpBB phpbbActivatedGroupId: 2 + # IP Board table prefix defined during the IP Board installation process + IPBTablePrefix: 'ipb_' + # IP Board default group ID; 3 is the default registered group defined by IP Board + IPBActivatedGroupId: 3 + # XenForo default group ID; 2 is the default registered group defined by Xenforo + XFActivatedGroupId: 2 # Wordpress prefix defined during WordPress installation wordpressTablePrefix: 'wp_' settings: @@ -444,8 +450,28 @@ Security: # Before a user logs in, various properties are temporarily removed from the player, # such as OP status, ability to fly, and walk/fly speed. # Once the user is logged in, we add back the properties we previously saved. -# In this section, you may define how the properties should be restored. +# In this section, you may define how these properties should be handled. limbo: + persistence: + # Besides storing the data in memory, you can define if/how the data should be persisted + # on disk. This is useful in case of a server crash, so next time the server starts we can + # properly restore things like OP status, ability to fly, and walk/fly speed. + # DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file, + # SINGLE_FILE: all data in one single file (only if you have a small server!) + # SEGMENT_FILES: distributes players into different buckets based on their UUID. See below. + type: 'INDIVIDUAL_FILES' + # This setting only affects SEGMENT_FILES persistence. The segment file + # persistence attempts to reduce the number of files by distributing players into various + # buckets based on their UUID. This setting defines into how many files the players should + # be distributed. Possible values: ONE, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR, + # ONE_TWENTY for 128, TWO_FIFTY for 256. + # For example, if you expect 100 non-logged in players, setting to SIXTEEN will average + # 6.25 players per file (100 / 16). If you set to ONE, like persistence SINGLE_FILE, only + # one file will be used. Contrary to SINGLE_FILE, it won't keep the entries in cache, which + # may deliver different results in terms of performance. + # Note: if you change this setting all data will be migrated. If you have a lot of data, + # change this setting only on server restart, not with /authme reload. + segmentDistribution: 'SIXTEEN' # Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE. # RESTORE sets back the old property from the player. restoreAllowFlight: 'RESTORE' @@ -485,4 +511,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 Tue Mar 14 16:42:26 EDT 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:33 CET 2017 diff --git a/docs/translations.md b/docs/translations.md index 689c303d8..66493be46 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,34 +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 | 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 | 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 | 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 | 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 | 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 | 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 -[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 +[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 95% | bar +[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 85% | bar +[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 85% | bar +[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 85% | bar +[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 95% | bar +[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 53% | bar +[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 56% | bar +[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 95% | bar +[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 60% | bar +[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 84% | bar +[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 60% | bar +[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 95% | bar +[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 61% | bar +[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 45% | bar +[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 85% | bar +[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 95% | bar +[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 95% | bar +[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 84% | bar +[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 95% | bar +[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 39% | bar +[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 95% | bar +[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 79% | bar +[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 85% | bar +[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 95% | bar +[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 69% | bar +[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 82% | bar +[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 69% | bar --- -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 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:32 CET 2017 diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 663155637..591579c6b 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -287,8 +287,8 @@ public class CommandInitializer { .labels("converter", "convert", "conv") .description("Converter command") .detailedDescription("Converter command for AuthMeReloaded.") - .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " + - "royalauth / vauth / sqliteToSql / mysqlToSqlite", false) + .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " + + "royalauth / vauth / sqliteToSql / mysqlToSqlite", false) .permission(AdminPermission.CONVERTER) .executableCommand(ConverterCommand.class) .register(); @@ -418,8 +418,8 @@ public class CommandInitializer { .parent(EMAIL_BASE) .labels("recover", "recovery", "recoveremail", "recovermail") .description("Recover password using email") - .detailedDescription("Recover your account using an Email address by sending a mail containing " + - "a new password.") + .detailedDescription("Recover your account using an Email address by sending a mail containing " + + "a new password.") .withArgument("email", "Email address", false) .permission(PlayerPermission.RECOVER_EMAIL) .executableCommand(RecoverEmailCommand.class) @@ -430,7 +430,7 @@ public class CommandInitializer { .parent(EMAIL_BASE) .labels("code") .description("Submit code to recover password") - .detailedDescription("Recover your account by submitting an emailed code to your email.") + .detailedDescription("Recover your account by submitting a code delivered to your email.") .withArgument("code", "Recovery code", false) .permission(PlayerPermission.RECOVER_EMAIL) .executableCommand(ProcessCodeCommand.class) diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java index befc1149a..26f48daa2 100644 --- a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java +++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java @@ -30,9 +30,6 @@ public class PasswordRecoveryService implements Reloadable { @Inject private CommonService commonService; - @Inject - private RecoveryCodeService codeService; - @Inject private DataSource dataSource; @@ -129,7 +126,7 @@ public class PasswordRecoveryService implements Reloadable { * @param player The player to check. * @return True if the player is not on cooldown. */ - public boolean checkEmailCooldown(Player player) { + private boolean checkEmailCooldown(Player player) { Duration waitDuration = emailCooldown.getExpiration(player.getName().toLowerCase()); if (waitDuration.getDuration() > 0) { String durationText = messages.formatDuration(waitDuration); diff --git a/src/main/resources/recovery_code_email.html b/src/main/resources/recovery_code_email.html index e5614f4f4..fc7327a1e 100644 --- a/src/main/resources/recovery_code_email.html +++ b/src/main/resources/recovery_code_email.html @@ -2,7 +2,7 @@

    You have requested to reset your password on . To reset it, - please use the recovery code : /email recover [email] . + please use the recovery code : /email code .

    The code expires in hours. From 32a664ef59edb61ae2dca51666859a8941c49c41 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 23 Mar 2017 10:34:28 +0100 Subject: [PATCH 111/125] Update checkstyle config and CodeClimate exclusions - Add new checkstyle checks: require Javadoc on large private methods, default in switch, declaration order & others - Update path exclusions in CodeClimate config to match newly renamed classes (e.g. PHPBB -> PhpBB) - Create consistency check testing that excluded paths exist as classes - Fix some trivial violations --- .checkstyle.xml | 20 +++++++- .codeclimate.yml | 6 +-- src/main/java/fr/xephi/authme/api/NewAPI.java | 3 +- .../authme/command/CommandInitializer.java | 4 ++ .../executable/authme/VersionCommand.java | 6 +-- .../authme/debug/TestEmailSender.java | 10 ++-- .../command/help/HelpMessagesService.java | 4 +- .../fr/xephi/authme/datasource/MySQL.java | 32 ++++++------ .../converter/RakamakConverter.java | 6 +-- .../fr/xephi/authme/mail/EmailService.java | 16 +++--- .../{SendMailSSL.java => SendMailSsl.java} | 4 +- .../authme/permission/AuthGroupHandler.java | 4 +- .../authme/permission/PermissionsManager.java | 11 +++- .../handlers/PermissionHandler.java | 8 +-- .../process/login/AsynchronousLogin.java | 1 + .../AbstractPasswordRegisterExecutor.java | 2 + .../executors/RegistrationExecutor.java | 2 + .../executors/RegistrationMethod.java | 2 + .../xephi/authme/service/BukkitService.java | 7 +-- .../authme/settings/EnumSetProperty.java | 2 + .../authme/util/expiring/TimedCounter.java | 5 +- .../xephi/authme/CodeClimateConfigTest.java | 51 +++++++++++++++++++ .../command/executable/HelpCommandTest.java | 2 +- .../debug/DebugSectionConsistencyTest.java | 5 +- .../xephi/authme/mail/EmailServiceTest.java | 48 ++++++++--------- ...dMailSSLTest.java => SendMailSslTest.java} | 14 ++--- .../settings/SettingsConsistencyTest.java | 3 +- 27 files changed, 185 insertions(+), 93 deletions(-) rename src/main/java/fr/xephi/authme/mail/{SendMailSSL.java => SendMailSsl.java} (98%) create mode 100644 src/test/java/fr/xephi/authme/CodeClimateConfigTest.java rename src/test/java/fr/xephi/authme/mail/{SendMailSSLTest.java => SendMailSslTest.java} (94%) diff --git a/.checkstyle.xml b/.checkstyle.xml index 8b8b4969a..00141bd6b 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -55,7 +55,9 @@ + + @@ -111,9 +113,11 @@ + + @@ -145,9 +149,21 @@ - - + + + + + + + + + arguments) { - if (!sendMailSSL.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; @@ -87,7 +87,7 @@ class TestEmailSender implements DebugSection { private boolean sendTestEmail(String email) { HtmlEmail htmlEmail; try { - htmlEmail = sendMailSSL.initializeMail(email); + htmlEmail = sendMailSsl.initializeMail(email); } catch (EmailException e) { ConsoleLogger.logException("Failed to create email for sample email:", e); return false; @@ -96,6 +96,6 @@ class TestEmailSender implements DebugSection { 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); + return sendMailSsl.sendEmail(message, htmlEmail); } } diff --git a/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java b/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java index c2cbaff99..7058c0cb8 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java @@ -81,8 +81,8 @@ public class HelpMessagesService implements Reloadable { public String getMessage(DefaultPermission defaultPermission) { // e.g. {default_permissions_path}.opOnly for DefaultPermission.OP_ONLY - String path = DEFAULT_PERMISSIONS_PATH + - CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name()); + String path = DEFAULT_PERMISSIONS_PATH + + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name()); return messageFileHandler.getMessage(path); } diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 4e6937d4a..6e09b5853 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -31,7 +31,7 @@ import java.util.Set; public class MySQL implements DataSource { - private boolean useSSL; + private boolean useSsl; private String host; private String port; private String username; @@ -45,10 +45,10 @@ public class MySQL implements DataSource { private HikariDataSource ds; private String phpBbPrefix; - private String IPBPrefix; + private String ipbPrefix; private int phpBbGroup; - private int IPBGroup; - private int XFGroup; + private int ipbGroup; + private int xfGroup; private String wordpressPrefix; public MySQL(Settings settings) throws ClassNotFoundException, SQLException { @@ -99,15 +99,15 @@ 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.XFGroup = settings.getProperty(HooksSettings.XF_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) { poolSize = Utils.getCoreCount()*3; } - this.useSSL = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL); + this.useSsl = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL); } private void setConnectionArguments() { @@ -125,7 +125,7 @@ public class MySQL implements DataSource { ds.setPassword(this.password); // Request mysql over SSL - ds.addDataSourceProperty("useSSL", useSSL); + ds.addDataSourceProperty("useSSL", useSsl); // Encoding ds.addDataSourceProperty("characterEncoding", "utf8"); @@ -347,23 +347,23 @@ public class MySQL implements DataSource { rs = pst.executeQuery(); if (rs.next()){ // Update player group in core_members - sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;"; + sql = "UPDATE " + ipbPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;"; pst2 = con.prepareStatement(sql); - pst2.setInt(1, IPBGroup); + 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 + "=?;"; + 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 + "=?;"; + sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;"; pst2 = con.prepareStatement(sql); pst2.setLong(1, time); pst2.setString(2, auth.getNickname()); @@ -531,14 +531,14 @@ public class MySQL implements DataSource { // 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.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.setInt(1, xfGroup); pst2.setString(2, auth.getNickname()); pst2.executeUpdate(); pst2.close(); @@ -557,7 +557,7 @@ public class MySQL implements DataSource { 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.setInt(2, xfGroup); pst2.setString(3, "1"); pst2.executeUpdate(); pst2.close(); 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 42a8ecf1b..4257f0aa7 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java @@ -46,7 +46,7 @@ public class RakamakConverter implements Converter { String ipFileName = settings.getProperty(ConverterSettings.RAKAMAK_IP_FILE_NAME); File source = new File(pluginFolder, fileName); File ipFiles = new File(pluginFolder, ipFileName); - Map playerIP = new HashMap<>(); + Map playerIp = new HashMap<>(); Map playerPassword = new HashMap<>(); try { BufferedReader ipFile = new BufferedReader(new FileReader(ipFiles)); @@ -56,7 +56,7 @@ public class RakamakConverter implements Converter { while ((tempLine = ipFile.readLine()) != null) { if (tempLine.contains("=")) { String[] args = tempLine.split("="); - playerIP.put(args[0], args[1]); + playerIp.put(args[0], args[1]); } } } @@ -75,7 +75,7 @@ public class RakamakConverter implements Converter { for (Entry m : playerPassword.entrySet()) { String playerName = m.getKey(); HashedPassword psw = playerPassword.get(playerName); - String ip = useIp ? playerIP.get(playerName) : "127.0.0.1"; + 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/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java index cb7c86de6..5eb588d18 100644 --- a/src/main/java/fr/xephi/authme/mail/EmailService.java +++ b/src/main/java/fr/xephi/authme/mail/EmailService.java @@ -25,18 +25,18 @@ public class EmailService { private final File dataFolder; private final String serverName; private final Settings settings; - private final SendMailSSL sendMailSSL; + private final SendMailSsl sendMailSsl; @Inject - EmailService(@DataFolder File dataFolder, Server server, Settings settings, SendMailSSL sendMailSSL) { + EmailService(@DataFolder File dataFolder, Server server, Settings settings, SendMailSsl sendMailSsl) { this.dataFolder = dataFolder; this.serverName = server.getServerName(); this.settings = settings; - this.sendMailSSL = sendMailSSL; + this.sendMailSsl = sendMailSsl; } public boolean hasAllInformation() { - return sendMailSSL.hasAllInformation(); + return sendMailSsl.hasAllInformation(); } @@ -56,7 +56,7 @@ public class EmailService { HtmlEmail email; try { - email = sendMailSSL.initializeMail(mailAddress); + email = sendMailSsl.initializeMail(mailAddress); } catch (EmailException e) { ConsoleLogger.logException("Failed to create email with the given settings:", e); return false; @@ -75,7 +75,7 @@ public class EmailService { } } - boolean couldSendEmail = sendMailSSL.sendEmail(mailText, email); + boolean couldSendEmail = sendMailSsl.sendEmail(mailText, email); FileUtils.delete(file); return couldSendEmail; } @@ -83,7 +83,7 @@ public class EmailService { public boolean sendRecoveryCode(String name, String email, String code) { HtmlEmail htmlEmail; try { - htmlEmail = sendMailSSL.initializeMail(email); + htmlEmail = sendMailSsl.initializeMail(email); } catch (EmailException e) { ConsoleLogger.logException("Failed to create email for recovery code:", e); return false; @@ -91,7 +91,7 @@ public class EmailService { String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(), name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID)); - return sendMailSSL.sendEmail(message, htmlEmail); + return sendMailSsl.sendEmail(message, htmlEmail); } private File generateImage(String name, String newPass) throws IOException { diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSsl.java similarity index 98% rename from src/main/java/fr/xephi/authme/mail/SendMailSSL.java rename to src/main/java/fr/xephi/authme/mail/SendMailSsl.java index ece409005..34011595f 100644 --- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java +++ b/src/main/java/fr/xephi/authme/mail/SendMailSsl.java @@ -24,7 +24,7 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD; /** * Sends emails to players on behalf of the server. */ -public class SendMailSSL { +public class SendMailSsl { @Inject private Settings settings; @@ -67,7 +67,7 @@ public class SendMailSSL { } public boolean sendEmail(String content, HtmlEmail email) { - Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader()); + 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 MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java index 5019d316d..2759b636a 100644 --- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java +++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java @@ -79,8 +79,8 @@ public class AuthGroupHandler implements Reloadable { throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'"); } - ConsoleLogger.debug( - () -> player.getName() + " changed to " + groupType + ": has groups " + permissionsManager.getGroups(player)); + ConsoleLogger.debug(() -> player.getName() + " changed to " + + groupType + ": has groups " + permissionsManager.getGroups(player)); } /** diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java index 497c99ab3..aac2f4bba 100644 --- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java +++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java @@ -74,7 +74,7 @@ public class PermissionsManager implements Reloadable { // Loop through all the available permissions system types for (PermissionsSystemType type : PermissionsSystemType.values()) { try { - PermissionHandler handler = getPermissionHandler(type); + PermissionHandler handler = createPermissionHandler(type); if (handler != null) { // Show a success message and return this.handler = handler; @@ -91,7 +91,14 @@ public class PermissionsManager implements Reloadable { ConsoleLogger.info("No supported permissions system found! Permissions are disabled!"); } - private PermissionHandler getPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException { + /** + * Creates a permission handler for the provided permission systems if possible. + * + * @param type the permission systems type for which to create a corresponding permission handler + * @return the permission handler, or {@code null} if not possible + * @throws PermissionHandlerException during initialization of the permission handler + */ + private PermissionHandler createPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException { // Try to find the plugin for the current permissions system Plugin plugin = pluginManager.getPlugin(type.getPluginName()); 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 36f6497ce..50816ce01 100644 --- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java +++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java @@ -17,7 +17,7 @@ public interface PermissionHandler { * @param group The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ boolean addToGroup(Player player, String group); @@ -47,7 +47,7 @@ public interface PermissionHandler { * @param group The group name. * * @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. + * False is also returned if groups aren't supported by the used permissions system. */ default boolean isInGroup(Player player, String group) { return getGroups(player).contains(group); @@ -60,7 +60,7 @@ public interface PermissionHandler { * @param group The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ boolean removeFromGroup(Player player, String group); @@ -72,7 +72,7 @@ public interface PermissionHandler { * @param group The name of the group. * * @return True if succeed, false otherwise. - * False is also returned if this feature isn't supported for the current permissions system. + * False is also returned if this feature isn't supported for the current permissions system. */ boolean setGroup(Player player, String group); 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 b246bf594..7df346e44 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -96,6 +96,7 @@ public class AsynchronousLogin implements AsynchronousProcess { * Checks the precondition for authentication (like user known) and returns * the player's {@link PlayerAuth} object. * + * @param player the player to check * @return the PlayerAuth object, or {@code null} if the player doesn't exist or may not log in * (e.g. because he is already logged in) */ diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java index 9f0ee7248..179aa59df 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java @@ -17,6 +17,8 @@ import javax.inject.Inject; /** * Registration executor for registration methods where the password * is supplied by the user. + * + * @param

    the parameters type */ abstract class AbstractPasswordRegisterExecutor

    implements RegistrationExecutor

    { diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java index ad18bf924..32bfd9517 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java @@ -4,6 +4,8 @@ import fr.xephi.authme.data.auth.PlayerAuth; /** * Performs the registration action. + * + * @param

    the registration parameters type */ public interface RegistrationExecutor

    { diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java index 9232332ef..f5f38b866 100644 --- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java +++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java @@ -8,6 +8,8 @@ package fr.xephi.authme.process.register.executors; * classes which perform this registration method. This is essentially a typed enum * as passing a constant of this class along with a parameters object to a method can * be restricted to the correct parameters type. + * + * @param

    the registration parameters type the method uses */ public final class RegistrationMethod

    { diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java index e945cebd6..4d94b62d5 100644 --- a/src/main/java/fr/xephi/authme/service/BukkitService.java +++ b/src/main/java/fr/xephi/authme/service/BukkitService.java @@ -44,7 +44,7 @@ public class BukkitService implements SettingsDependent { @Inject BukkitService(AuthMe authMe, Settings settings) { this.authMe = authMe; - getOnlinePlayersIsCollection = initializeOnlinePlayersIsCollectionField(); + getOnlinePlayersIsCollection = doesOnlinePlayersMethodReturnCollection(); reload(settings); } @@ -301,11 +301,12 @@ public class BukkitService implements SettingsDependent { /** * Method run upon initialization to verify whether or not the Bukkit implementation - * returns the online players as a Collection. + * returns the online players as a {@link Collection}. * + * @return true if a collection is returned by the bukkit implementation, false otherwise * @see #getOnlinePlayers() */ - private static boolean initializeOnlinePlayersIsCollectionField() { + private static boolean doesOnlinePlayersMethodReturnCollection() { try { Method method = Bukkit.class.getDeclaredMethod("getOnlinePlayers"); return method.getReturnType() == Collection.class; diff --git a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java index 37ebba815..901c9fc1e 100644 --- a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java +++ b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java @@ -12,6 +12,8 @@ import static com.google.common.collect.Sets.newHashSet; /** * Property whose value is a set of entries of a given enum. + * + * @param the enum type */ public class EnumSetProperty> extends Property> { 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 2e19b5a28..e5ef899d6 100644 --- a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java +++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java @@ -7,6 +7,8 @@ import java.util.concurrent.TimeUnit; *

    * 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. + * + * @param the type of the key */ public class TimedCounter extends ExpiringMap { @@ -47,8 +49,7 @@ public class TimedCounter extends ExpiringMap { if (e != null) { if (e.getValue() <= 0) { remove(key); - } - else { + } else { entries.put(key, new ExpiringEntry<>(e.getValue() - 1, e.getExpiration())); } } diff --git a/src/test/java/fr/xephi/authme/CodeClimateConfigTest.java b/src/test/java/fr/xephi/authme/CodeClimateConfigTest.java new file mode 100644 index 000000000..928ad2a9d --- /dev/null +++ b/src/test/java/fr/xephi/authme/CodeClimateConfigTest.java @@ -0,0 +1,51 @@ +package fr.xephi.authme; + +import fr.xephi.authme.util.Utils; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Test; + +import java.io.File; +import java.util.List; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +/** + * Consistency test for the CodeClimate configuration file. + */ +public class CodeClimateConfigTest { + + private static final String CONFIG_FILE = ".codeclimate.yml"; + + @Test + public void shouldHaveExistingClassesInExclusions() { + // given + FileConfiguration configuration = YamlConfiguration.loadConfiguration(new File(CONFIG_FILE)); + List excludePaths = configuration.getStringList("exclude_paths"); + + // when / then + assertThat(excludePaths, not(empty())); + for (String path : excludePaths) { + String className = convertPathToQualifiedClassName(path); + assertThat("No class corresponds to excluded path '" + path + "'", + Utils.isClassLoaded(className), equalTo(true)); + } + } + + private static String convertPathToQualifiedClassName(String path) { + // Note ljacqu 20170323: In the future, we could have legitimate exclusions that don't fulfill these checks, + // in which case this test needs to be adapted accordingly. + if (!path.startsWith(TestHelper.SOURCES_FOLDER)) { + throw new IllegalArgumentException("Unexpected path '" + path + "': expected to start with sources folder"); + } else if (!path.endsWith(".java")) { + throw new IllegalArgumentException("Expected path '" + path + "' to end with '.java'"); + } + + return path.substring(0, path.length() - ".java".length()) // strip ending .java + .substring(TestHelper.SOURCES_FOLDER.length()) // strip starting src/main/java + .replace('/', '.'); // replace '/' to '.' + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java index 271f11741..bb5694e94 100644 --- a/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/HelpCommandTest.java @@ -26,7 +26,7 @@ import static fr.xephi.authme.command.help.HelpProvider.SHOW_COMMAND; import static fr.xephi.authme.command.help.HelpProvider.SHOW_DESCRIPTION; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; 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 index 18ab9fd22..c0e35b80a 100644 --- 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 @@ -1,6 +1,7 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.ClassCollector; +import fr.xephi.authme.TestHelper; import org.junit.BeforeClass; import org.junit.Test; @@ -22,8 +23,8 @@ public class DebugSectionConsistencyTest { @BeforeClass public static void collectClasses() { - debugClasses = new ClassCollector("src/main/java", "fr/xephi/authme/command/executable/authme/debug") - .collectClasses(); + debugClasses = new ClassCollector( + TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "command/executable/authme/debug").collectClasses(); } @Test diff --git a/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java b/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java index 9feb94237..43cbaa61a 100644 --- a/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java +++ b/src/test/java/fr/xephi/authme/mail/EmailServiceTest.java @@ -48,7 +48,7 @@ public class EmailServiceTest { @Mock private Server server; @Mock - private SendMailSSL sendMailSSL; + private SendMailSsl sendMailSsl; @DataFolder private File dataFolder; @@ -66,7 +66,7 @@ public class EmailServiceTest { 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); + given(sendMailSsl.hasAllInformation()).willReturn(true); } @Test @@ -82,17 +82,17 @@ public class EmailServiceTest { .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); + 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"); + verify(sendMailSsl).initializeMail("user@example.com"); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + verify(sendMailSsl).sendEmail(messageCaptor.capture(), eq(email)); assertThat(messageCaptor.getValue(), equalTo("Hi Player, your new password for serverName is new_password")); } @@ -100,15 +100,15 @@ public class EmailServiceTest { @Test public void shouldHandleMailCreationError() throws EmailException { // given - doThrow(EmailException.class).when(sendMailSSL).initializeMail(anyString()); + 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)); + verify(sendMailSsl).initializeMail("user@example.com"); + verify(sendMailSsl, never()).sendEmail(anyString(), any(HtmlEmail.class)); } @Test @@ -117,17 +117,17 @@ public class EmailServiceTest { 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); + 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"); + verify(sendMailSsl).initializeMail("user@example.com"); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + verify(sendMailSsl).sendEmail(messageCaptor.capture(), eq(email)); assertThat(messageCaptor.getValue(), equalTo("Hi bobby, your new pass is myPassw0rd")); } @@ -138,32 +138,32 @@ public class EmailServiceTest { 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); + 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"); + verify(sendMailSsl).initializeMail("tim@example.com"); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + 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); + 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)); + verify(sendMailSsl).initializeMail("player@example.org"); + verify(sendMailSsl, never()).sendEmail(anyString(), any(HtmlEmail.class)); } @Test @@ -173,17 +173,17 @@ public class EmailServiceTest { 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); + 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"); + verify(sendMailSsl).initializeMail("user@example.com"); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); - verify(sendMailSSL).sendEmail(messageCaptor.capture(), eq(email)); + 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 similarity index 94% rename from src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java rename to src/test/java/fr/xephi/authme/mail/SendMailSslTest.java index 74318498f..cd5b21523 100644 --- a/src/test/java/fr/xephi/authme/mail/SendMailSSLTest.java +++ b/src/test/java/fr/xephi/authme/mail/SendMailSslTest.java @@ -28,13 +28,13 @@ import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; /** - * Test for {@link SendMailSSL}. + * Test for {@link SendMailSsl}. */ @RunWith(DelayedInjectionRunner.class) -public class SendMailSSLTest { +public class SendMailSslTest { @InjectDelayed - private SendMailSSL sendMailSSL; + private SendMailSsl sendMailSsl; @Mock private Settings settings; @@ -57,7 +57,7 @@ public class SendMailSSLTest { @Test public void shouldHaveAllInformation() { // given / when / then - assertThat(sendMailSSL.hasAllInformation(), equalTo(true)); + assertThat(sendMailSsl.hasAllInformation(), equalTo(true)); } @Test @@ -73,7 +73,7 @@ public class SendMailSSLTest { given(settings.getProperty(PluginSettings.LOG_LEVEL)).willReturn(LogLevel.DEBUG); // when - HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); + HtmlEmail email = sendMailSsl.initializeMail("recipient@example.com"); // then assertThat(email, not(nullValue())); @@ -99,7 +99,7 @@ public class SendMailSSLTest { given(settings.getProperty(EmailSettings.MAIL_SENDER_NAME)).willReturn(senderName); // when - HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); + HtmlEmail email = sendMailSsl.initializeMail("recipient@example.com"); // then assertThat(email, not(nullValue())); @@ -122,7 +122,7 @@ public class SendMailSSLTest { given(settings.getProperty(EmailSettings.MAIL_ACCOUNT)).willReturn(senderMail); // when - HtmlEmail email = sendMailSSL.initializeMail("recipient@example.com"); + HtmlEmail email = sendMailSsl.initializeMail("recipient@example.com"); // then assertThat(email, not(nullValue())); diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index cddc0cab3..c3648a7f1 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -9,6 +9,7 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.ReflectionTestUtils; +import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.settings.properties.AuthMeSettingsRetriever; import fr.xephi.authme.settings.properties.DatabaseSettings; @@ -126,7 +127,7 @@ public class SettingsConsistencyTest { private List getSectionCommentMethods() { // Find all SettingsHolder classes List> settingsClasses = - new ClassCollector("src/main/java", "fr/xephi/authme/settings/properties/") + new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "settings/properties/") .collectClasses(SettingsHolder.class); checkArgument(!settingsClasses.isEmpty(), "Could not find any SettingsHolder classes"); From dfd81b069addae74886ebaa3417e2c2007ffdf2b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 23 Mar 2017 10:43:29 +0100 Subject: [PATCH 112/125] Fix titles in readme --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2138ed4f1..0bd06437f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

    The most used authentication plugin for Spigot and CraftBukkit!


    -#####Links and Contacts: +##### Links and Contacts: - GitHub pages: - [Main](https://github.com/Xephi/AuthMeReloaded) (**release sources, issue tracker!**) @@ -30,24 +30,24 @@
    -#####Compiling requirements: +##### Compiling requirements: >- JDK 1.8 >- Maven >- Git/Github (Optional) -#####How to compile the project: +##### How to compile the project: >- Clone the project with Git/Github >- Execute command "mvn clean package" -#####Running requirements: +##### Running requirements: >- Java 1.8 >- TacoSpigot, PaperSpigot, Spigot or CraftBukkit (1.7.10, 1.8.X, 1.9.X, 1.10.X, 1.11.X) >- ProtocolLib (optional, required by some features)
    -###Plugin Description: +### Plugin Description: -#####"The best authentication plugin for the Bukkit/Spigot API!" +##### "The best authentication plugin for the Bukkit/Spigot API!" Prevent username stealing on your server!
    Use it to secure your Offline mode server or to increase your Online mode server's protection! @@ -60,7 +60,7 @@ Each command and every feature can be enabled or disabled from our well structur You can also create your own translation file and, if you want, you can share it with us! :) -####Features: +#### Features:
    • E-Mail Recovery System !!!
    • Username spoofing protection.
    • @@ -99,18 +99,18 @@ You can also create your own translation file and, if you want, you can share it
    • Import your old database from other plugins like Rakamak, xAuth, CrazyLogin, RoyalAuth and vAuth!
    -####Configuration +#### Configuration How to configure Authme -####Email Recovery Dependency +#### Email Recovery Dependency How to configure email recovery system? -####Commands +#### Commands [Command list and usage](https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/commands.md) -####Permissions +#### Permissions - authme.player.* - for all user commands - authme.admin.* - for all admin commands - [List of all permission nodes](http://github.com/AuthMe-Team/AuthMeReloaded/blob/master/docs/permission_nodes.md) -####How To +#### How To - [How to import database from xAuth](http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-xauth/) - [Website integration](http://dev.bukkit.org/server-mods/authme-reloaded/pages/web-site-integration/) - [How to convert from Rakamak](http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-rakamak/) @@ -118,14 +118,14 @@ You can also create your own translation file and, if you want, you can share it
    -#####Sponsor +##### Sponsor GameHosting.it is leader in Italy as Game Server Provider. With its own DataCenter offers Anti-DDoS solutions at affordable prices. Game Server of Minecraft based on Multicraft are equipped with the latest technology in hardware. [![GameHosting](http://www.gamehosting.it/images/bn3.png)](http://www.gamehosting.it) -#####Credits +##### Credits

    Team members: look at the member list

    Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka

    Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex

    -#####GeoIP License +##### GeoIP License This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com From 3e95d30c6bb9ee4414cfc789a9f3dfd72566681c Mon Sep 17 00:00:00 2001 From: ljacqu Date: Thu, 23 Mar 2017 23:15:04 +0100 Subject: [PATCH 113/125] Improve dependency graph task - Simplify logic for converting a dependency description to its display name - Add new common supertypes --- .../tools/dependencygraph/DrawDependency.java | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/test/java/tools/dependencygraph/DrawDependency.java b/src/test/java/tools/dependencygraph/DrawDependency.java index 34756ebb3..98de28be4 100644 --- a/src/test/java/tools/dependencygraph/DrawDependency.java +++ b/src/test/java/tools/dependencygraph/DrawDependency.java @@ -3,16 +3,22 @@ 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 ch.jalu.injector.utils.ReflectionUtils; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.TestHelper; import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.command.executable.authme.debug.DebugCommand; +import fr.xephi.authme.data.limbo.persistence.LimboPersistence; import fr.xephi.authme.datasource.converter.Converter; import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.initialization.factory.SingletonStore; import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.SynchronousProcess; +import fr.xephi.authme.process.register.executors.RegistrationExecutor; import fr.xephi.authme.security.crypts.EncryptionMethod; import org.bukkit.event.Listener; import tools.utils.ToolTask; @@ -20,13 +26,14 @@ import tools.utils.ToolsConstants; import java.io.IOException; import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Scanner; /** @@ -36,8 +43,7 @@ public class DrawDependency implements ToolTask { private static final String DOT_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "dependencygraph/graph.dot"; - private static final List> SUPER_TYPES = ImmutableList.of(ExecutableCommand.class, - SynchronousProcess.class, AsynchronousProcess.class, EncryptionMethod.class, Converter.class, Listener.class); + private static final List> SUPER_TYPES = buildSuperTypesList(); /** Annotation types by which dependencies are identified. */ private static final List> ANNOTATION_TYPES = ImmutableList.of(DataFolder.class); @@ -113,6 +119,24 @@ public class DrawDependency implements ToolTask { return clazz; } + /** + * Returns the parameter of generic container classes, otherwise the input class. + * This is interesting so that a dependency in a class to {@code Factory} is + * rendered as a dependency to {@code Foo}, not to {@code Factory}. + * + * @param clazz class of the dependency + * @param genericType generic type of the dependency + * @return the class to use to render the dependency + */ + private Class unwrapGenericClass(Class clazz, Type genericType) { + if (clazz == Factory.class || clazz == SingletonStore.class) { + Class parameterType = ReflectionUtils.getGenericType(genericType); + Objects.requireNonNull(parameterType, "Parameter type for '" + clazz + "' should be a concrete class"); + return parameterType; + } + return clazz; + } + private List getDependencies(Class clazz) { Instantiation instantiation = new StandardInjectionProvider().safeGet(clazz); return instantiation == null ? null : formatInjectionDependencies(instantiation); @@ -127,21 +151,15 @@ public class DrawDependency implements ToolTask { * @return list of dependencies in a friendly format */ private List formatInjectionDependencies(Instantiation injection) { - List descriptions = injection.getDependencies(); - final int totalDependencies = descriptions.size(); - Class[] dependencies = new Class[totalDependencies]; - Class[] annotations = new Class[totalDependencies]; - for (int i = 0; i < descriptions.size(); ++i) { - dependencies[i] = descriptions.get(i).getType(); - annotations[i] = getRelevantAnnotationClass(descriptions.get(i).getAnnotations()); - } - - List result = new ArrayList<>(dependencies.length); - for (int i = 0; i < dependencies.length; ++i) { - if (annotations[i] != null) { - result.add("@" + annotations[i].getSimpleName()); + List descriptions = injection.getDependencies(); + List result = new ArrayList<>(descriptions.size()); + for (DependencyDescription dependency : descriptions) { + Class annotation = getRelevantAnnotationClass(dependency.getAnnotations()); + if (annotation != null) { + result.add("@" + annotation.getSimpleName()); } else { - result.add(mapToSuper(dependencies[i]).getSimpleName()); + Class clazz = unwrapGenericClass(dependency.getType(), dependency.getGenericType()); + result.add(mapToSuper(clazz).getSimpleName()); } } return result; @@ -169,12 +187,26 @@ public class DrawDependency implements ToolTask { dependencies.put(entry.getValue(), Boolean.TRUE); } - Iterator> it = foundDependencies.keys().iterator(); - while (it.hasNext()) { - Class clazz = it.next(); - if (Boolean.FALSE.equals(dependencies.get(clazz.getSimpleName()))) { - it.remove(); - } + foundDependencies.keys().removeIf( + clazz -> Boolean.FALSE.equals(dependencies.get(clazz.getSimpleName()))); + } + + /** + * @return list of classes in AuthMe which have multiple extensions + */ + private static List> buildSuperTypesList() { + try { + // Get package-private classes + Class debugSectionClass = Class.forName( + DebugCommand.class.getPackage().getName() + ".DebugSection"); + Class limboPersistenceClass = Class.forName( + LimboPersistence.class.getPackage().getName() + ".LimboPersistenceHandler"); + + return ImmutableList.of(ExecutableCommand.class, SynchronousProcess.class, AsynchronousProcess.class, + EncryptionMethod.class, Converter.class, Listener.class, RegistrationExecutor.class, debugSectionClass, + limboPersistenceClass); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); } } } From 2f90a45f4351d80c4b04d5ca9d07f3ebf2ed62d6 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 24 Mar 2017 21:26:34 +0100 Subject: [PATCH 114/125] #1036 Remove 'allowSpeed' option - Option is no longer needed as the plugin user can configure how/if the speeds should be restored now --- .../java/fr/xephi/authme/data/limbo/LimboServiceHelper.java | 3 +-- .../authme/settings/properties/RestrictionSettings.java | 6 ------ .../java/fr/xephi/authme/data/limbo/LimboServiceTest.java | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java index 7373212f7..6b3acfcb5 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java @@ -57,8 +57,7 @@ class LimboServiceHelper { player.setOp(false); player.setAllowFlight(false); - if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT) - && settings.getProperty(RestrictionSettings.REMOVE_SPEED)) { + if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) { player.setFlySpeed(0.0f); player.setWalkSpeed(0.0f); } 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 40de8ca69..009fb59a0 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -107,12 +107,6 @@ public final class RestrictionSettings implements SettingsHolder { public static final Property ALLOW_UNAUTHED_MOVEMENT = newProperty("settings.restrictions.allowMovement", false); - @Comment({ - "Should not authenticated players have speed = 0?", - "This will reset the fly/walk speed to default value after the login."}) - public static final Property REMOVE_SPEED = - newProperty("settings.restrictions.removeSpeed", true); - @Comment({ "After how many seconds should players who fail to login or register", "be kicked? Set to 0 to disable."}) diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java index 807024cd6..740d7a6b8 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -69,7 +69,6 @@ public class LimboServiceTest { @Before public void mockSettings() { given(settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)).willReturn(false); - given(settings.getProperty(RestrictionSettings.REMOVE_SPEED)).willReturn(true); } @Test From a2d62ea6d9d41e5b5f57f3b83533f51af8864000 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 24 Mar 2017 23:03:10 +0100 Subject: [PATCH 115/125] #1116 #1117 Add command usage hints and remove unused message keys - Add usage message on argument mismatch where available - Remove unused message keys - Create tool task to search for a message key's usages and to find unused keys --- .../authme/command/ExecutableCommand.java | 2 +- .../changepassword/ChangePasswordCommand.java | 5 + .../executable/email/AddEmailCommand.java | 5 + .../executable/email/ChangeEmailCommand.java | 6 ++ .../executable/email/RecoverEmailCommand.java | 7 +- .../unregister/UnregisterCommand.java | 10 ++ .../fr/xephi/authme/message/MessageKey.java | 11 +-- .../process/login/AsynchronousLogin.java | 12 ++- src/main/resources/messages/messages_bg.yml | 2 - src/main/resources/messages/messages_br.yml | 2 - src/main/resources/messages/messages_cz.yml | 2 - src/main/resources/messages/messages_de.yml | 2 - src/main/resources/messages/messages_en.yml | 2 - src/main/resources/messages/messages_es.yml | 2 - src/main/resources/messages/messages_eu.yml | 2 - src/main/resources/messages/messages_fi.yml | 2 - src/main/resources/messages/messages_fr.yml | 2 - src/main/resources/messages/messages_gl.yml | 2 - src/main/resources/messages/messages_hu.yml | 2 - src/main/resources/messages/messages_id.yml | 2 - src/main/resources/messages/messages_it.yml | 2 - src/main/resources/messages/messages_ko.yml | 2 - src/main/resources/messages/messages_lt.yml | 2 - src/main/resources/messages/messages_nl.yml | 2 - src/main/resources/messages/messages_pl.yml | 2 - src/main/resources/messages/messages_pt.yml | 2 - src/main/resources/messages/messages_ro.yml | 2 - src/main/resources/messages/messages_ru.yml | 2 - src/main/resources/messages/messages_sk.yml | 2 - src/main/resources/messages/messages_tr.yml | 2 - src/main/resources/messages/messages_uk.yml | 2 - src/main/resources/messages/messages_vn.yml | 2 - src/main/resources/messages/messages_zhcn.yml | 2 - src/main/resources/messages/messages_zhhk.yml | 2 - src/main/resources/messages/messages_zhmc.yml | 2 - src/main/resources/messages/messages_zhtw.yml | 2 - .../unregister/UnregisterCommandTest.java | 16 ++++ .../message/MessagesIntegrationTest.java | 4 +- .../tools/messages/CheckMessageKeyUsages.java | 91 +++++++++++++++++++ .../fr/xephi/authme/message/messages_test.yml | 2 +- .../xephi/authme/message/messages_test2.yml | 2 +- 41 files changed, 155 insertions(+), 74 deletions(-) create mode 100644 src/test/java/tools/messages/CheckMessageKeyUsages.java diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java index 6dd81ca80..54ee9e846 100644 --- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java +++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java @@ -19,7 +19,7 @@ public interface ExecutableCommand { void executeCommand(CommandSender sender, List arguments); /** - * Returns the message to show to the user if the command is used with the wrong commands. + * Returns the message to show to the user if the command is used with the wrong arguments. * If null is returned, the standard help (/command help) output is shown. * * @return the message explaining the command's usage, or {@code null} for default behavior diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java index 7c5e7d9dc..7c4b53c35 100644 --- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java @@ -54,4 +54,9 @@ public class ChangePasswordCommand extends PlayerCommand { protected String getAlternativeCommand() { return "/authme password "; } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_CHANGE_PASSWORD; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java index eae64fdb0..87e381047 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java @@ -32,4 +32,9 @@ public class AddEmailCommand extends PlayerCommand { commonService.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE); } } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_ADD_EMAIL; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java index 1f9051e38..aabe99bc3 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java @@ -1,6 +1,7 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.command.PlayerCommand; +import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import org.bukkit.entity.Player; @@ -22,4 +23,9 @@ public class ChangeEmailCommand extends PlayerCommand { management.performChangeEmail(player, playerMailOld, playerMailNew); } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_CHANGE_EMAIL; + } } 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 142a4b600..bd50cc743 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 @@ -53,7 +53,7 @@ public class RecoverEmailCommand extends PlayerCommand { return; } - PlayerAuth auth = dataSource.getAuth(playerName); // TODO: Create method to get email only + PlayerAuth auth = dataSource.getAuth(playerName); // TODO #1127: Create method to get email only if (auth == null) { commonService.send(player, MessageKey.USAGE_REGISTER); return; @@ -73,4 +73,9 @@ public class RecoverEmailCommand extends PlayerCommand { recoveryService.generateAndSendNewPassword(player, email); } } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_RECOVER_EMAIL; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java index 09b996a27..dd747e0e4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java @@ -38,4 +38,14 @@ public class UnregisterCommand extends PlayerCommand { // Unregister the player management.performUnregister(player, playerPass); } + + @Override + public MessageKey getArgumentsMismatchMessage() { + return MessageKey.USAGE_UNREGISTER; + } + + @Override + protected String getAlternativeCommand() { + return "/authme unregister "; + } } diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java index a54423862..f962ba4e1 100644 --- a/src/main/java/fr/xephi/authme/message/MessageKey.java +++ b/src/main/java/fr/xephi/authme/message/MessageKey.java @@ -20,9 +20,6 @@ public enum MessageKey { /** This user isn't registered! */ UNKNOWN_USER("unknown_user"), - /** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */ - UNSAFE_QUIT_LOCATION("unsafe_spawn"), - /** You're not logged in! */ NOT_LOGGED_IN("not_logged_in"), @@ -179,9 +176,6 @@ public enum MessageKey { /** Recovery email sent successfully! Please check your email inbox! */ RECOVERY_EMAIL_SENT_MESSAGE("email_send"), - /** A recovery email was already sent! You can discard it and send a new one using the command below: */ - RECOVERY_EMAIL_ALREADY_SENT_MESSAGE("email_exists"), - /** Your country is banned from this server! */ COUNTRY_BANNED_ERROR("country_banned"), @@ -227,7 +221,10 @@ public enum MessageKey { /** The recovery code is not correct! You have %count tries remaining. */ INCORRECT_RECOVERY_CODE("recovery_code_incorrect", "%count"), - /** You have exceeded the maximum number of attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one. */ + /** + * You have exceeded the maximum number of attempts to enter the recovery code. + * Use "/email recovery [email]" to generate a new one. + */ RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"), /** Recovery code entered correctly! */ 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 7df346e44..fa0c76ab1 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -9,6 +9,7 @@ import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; +import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PlayerPermission; @@ -64,6 +65,9 @@ public class AsynchronousLogin implements AsynchronousProcess { @Inject private LimboService limboService; + @Inject + private EmailService emailService; + AsynchronousLogin() { } @@ -189,6 +193,8 @@ public class AsynchronousLogin implements AsynchronousProcess { limboService.muteMessageTask(player); service.send(player, MessageKey.USAGE_CAPTCHA, captchaManager.getCaptchaCodeOrGenerateNew(player.getName())); + } else if (emailService.hasAllInformation()) { + service.send(player, MessageKey.FORGOT_PASSWORD_MESSAGE); } } } @@ -262,11 +268,7 @@ public class AsynchronousLogin implements AsynchronousProcess { } private void displayOtherAccounts(List auths, Player player) { - if (!service.getProperty(RestrictionSettings.DISPLAY_OTHER_ACCOUNTS)) { - return; - } - - if (auths.size() <= 1) { + if (!service.getProperty(RestrictionSettings.DISPLAY_OTHER_ACCOUNTS) || auths.size() <= 1) { return; } diff --git a/src/main/resources/messages/messages_bg.yml b/src/main/resources/messages/messages_bg.yml index 1ea7196ca..183f279c0 100644 --- a/src/main/resources/messages/messages_bg.yml +++ b/src/main/resources/messages/messages_bg.yml @@ -30,7 +30,6 @@ 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 @@ -82,7 +81,6 @@ 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Имейл адреса вече се използва, опитайте с друг.' diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index b9801f949..7adea7166 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -34,7 +34,6 @@ tempban_max_logins: '&cVocê foi temporariamente banido por tentar logar muitas max_reg: '&cVocê excedeu o número máximo de inscrições (%reg_count/%max_acc %reg_names) do seu IP!' no_perm: '&4Você não tem permissão para executar esta ação!' error: '&4Ocorreu um erro inesperado, por favor contacte um administrador!' -unsafe_spawn: '&cVocê deslogou em um local inseguro e foi teleportado parao Spawn do servidor!' kick_forvip: '&3Um jogador VIP juntou-se ao servidor enquanto ele estava cheio!' # AntiBot @@ -86,7 +85,6 @@ email_added: '&2Email adicionado com sucesso à sua conta!' email_confirm: '&cPor favor confirme seu endereço de email!' email_changed: '&2Troca de email com sucesso.!' email_send: '&2Recuperação de email enviada com sucesso! Por favor, verifique sua caixa de entrada de e-mail!' -email_exists: '&cUm e-mail de recuperação já foi enviado! Você pode descartá-lo e enviar um novo usando o comando abaixo:' email_show: '&2O seu endereço de e-mail atual é: &f%email' incomplete_email_settings: 'Erro: Nem todas as configurações necessárias estão definidas para o envio de e-mails. Entre em contato com um administrador.' email_already_used: '&4O endereço de e-mail já está sendo usado' diff --git a/src/main/resources/messages/messages_cz.yml b/src/main/resources/messages/messages_cz.yml index 55633d5f0..47d101efc 100644 --- a/src/main/resources/messages/messages_cz.yml +++ b/src/main/resources/messages/messages_cz.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cByl jsi dočasně zabanován za příliš mnoho neúspě max_reg: '&cPřekročil(a) jsi limit pro počet účtů (%reg_count/%max_acc %reg_names) z jedné IP adresy.' no_perm: '&cNa tento příkaz nemáš dostatečné pravomoce.' error: '&cVyskytla se chyba - kontaktujte prosím administrátora ...' -unsafe_spawn: '&cTvoje pozice při posledním odpojení byla nebezpečná, teleportuji tě proto na spawn!' kick_forvip: '&cOmlouváme se, ale VIP hráč se připojil na plný server!' # AntiBot @@ -82,7 +81,6 @@ email_added: '[AuthMe] Email přidán!' email_confirm: '[AuthMe] Potvrď prosím svůj email!' email_changed: '[AuthMe] Email změněn!' email_send: '[AuthMe] Email pro obnovení hesla odeslán!' -email_exists: '&cNový email byl odeslán! Můžeš ho zahodit a poslat jiný pomocí tohoto příkazu:' email_show: '&2Váš aktuální email je: &f%email' incomplete_email_settings: 'Chyba: chybí některé důležité informace pro odeslání emailu. Kontaktujte prosím admina.' email_already_used: '&4Tato emailová adresa je již používána' diff --git a/src/main/resources/messages/messages_de.yml b/src/main/resources/messages/messages_de.yml index ca2bc2fde..ec0fd224e 100644 --- a/src/main/resources/messages/messages_de.yml +++ b/src/main/resources/messages/messages_de.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cDu bist wegen zu vielen fehlgeschlagenen Login-Versuchen max_reg: '&cDu hast die maximale Anzahl an Accounts erreicht (%reg_count/%max_acc %reg_names).' no_perm: '&4Du hast keine Rechte, um diese Aktion auszuführen!' error: '&4Ein Fehler ist aufgetreten. Bitte kontaktiere einen Administrator.' -unsafe_spawn: '&cDeine Logoutposition war unsicher, du wurdest zum Spawn teleportiert' kick_forvip: '&3Ein VIP-Spieler hat den vollen Server betreten!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2E-Mail hinzugefügt!' email_confirm: '&cBitte bestätige deine E-Mail!' email_changed: '&2E-Mail aktualisiert!' email_send: '&2Wiederherstellungs-E-Mail wurde gesendet!' -email_exists: '&cEine Wiederherstellungs-E-Mail wurde bereits versandt! Nutze folgenden Befehl um eine neue E-Mail zu versenden:' email_show: '&2Deine aktuelle E-Mail-Adresse ist: &f%email' incomplete_email_settings: 'Fehler: Es wurden nicht alle notwendigen Einstellungen vorgenommen, um E-Mails zu senden. Bitte kontaktiere einen Administrator.' email_already_used: '&4Diese E-Mail-Adresse wird bereits genutzt.' diff --git a/src/main/resources/messages/messages_en.yml b/src/main/resources/messages/messages_en.yml index b6f6ec6a8..fc815663f 100644 --- a/src/main/resources/messages/messages_en.yml +++ b/src/main/resources/messages/messages_en.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cYou have been temporarily banned for failing to log in to max_reg: '&cYou have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection!' no_perm: '&4You don''t have the permission to perform this action!' error: '&4An unexpected error occurred, please contact an administrator!' -unsafe_spawn: '&cYour quit location was unsafe, you have been teleported to the world''s spawnpoint.' kick_forvip: '&3A VIP player has joined the server when it was full!' # AntiBot @@ -81,7 +80,6 @@ email_added: '&2Email address successfully added to your account!' email_confirm: '&cPlease confirm your email address!' email_changed: '&2Email address changed correctly!' email_send: '&2Recovery email sent successfully! Please check your email inbox!' -email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' email_show: '&2Your current email address is: &f%email' incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' email_already_used: '&4The email address is already being used' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index d2c322dd4..71018829b 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -33,7 +33,6 @@ tempban_max_logins: '&cHas sido expulsado temporalmente por intentar iniciar ses max_reg: '&fHas excedido la cantidad máxima de registros para tu cuenta' no_perm: '&cNo tienes permiso' error: '&fHa ocurrido un error. Por favor contacta al administrador.' -unsafe_spawn: '&fTu lugar de desconexión es inseguro, teletransportándote al punto inicial del mundo' kick_forvip: '&c¡Un jugador VIP ha ingresado al servidor lleno!' # AntiBot @@ -85,7 +84,6 @@ email_added: '[AuthMe] Email agregado !' email_confirm: '[AuthMe] Confirma tu Email !' email_changed: '[AuthMe] Email cambiado !' email_send: '[AuthMe] Correo de recuperación enviado !' -email_exists: '&c¡El correo de recuperación ya ha sido enviado! Puedes descartarlo y enviar uno nuevo utilizando el siguiente comando:' email_show: '&2Tu dirección de E-Mail actual es: &f%email' incomplete_email_settings: 'Error: no todos los ajustes necesarios se han configurado para enviar correos. Por favor, contacta con un administrador.' email_already_used: '&4La dirección Email ya está siendo usada' diff --git a/src/main/resources/messages/messages_eu.yml b/src/main/resources/messages/messages_eu.yml index 098c16aab..28ba966ab 100644 --- a/src/main/resources/messages/messages_eu.yml +++ b/src/main/resources/messages/messages_eu.yml @@ -31,7 +31,6 @@ not_logged_in: '&cSaioa hasi gabe!' max_reg: '&fKontuko 2 erabiltzaile bakarrik izan ditzakezu' no_perm: '&cBaimenik ez' error: '&fErrorea; Mesedez jarri kontaktuan administratzaile batekin' -unsafe_spawn: '&fSpawn-era telegarraiatu zara' kick_forvip: '&cVIP erabiltzaile bat sartu da zerbitzaria beteta zegoenean!' # AntiBot @@ -82,7 +81,6 @@ email_added: '[AuthMe] Emaila gehitu duzu !' email_confirm: '[AuthMe] Konfirmatu zure emaila !' email_changed: '[AuthMe] Emaila aldatua!' email_send: '[AuthMe] Berreskuratze emaila bidalita !' -# 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' diff --git a/src/main/resources/messages/messages_fi.yml b/src/main/resources/messages/messages_fi.yml index bf266218d..0f491ce62 100644 --- a/src/main/resources/messages/messages_fi.yml +++ b/src/main/resources/messages/messages_fi.yml @@ -31,7 +31,6 @@ not_logged_in: '&cEt ole kirjautunut sisään!' max_reg: '&fSinulla ei ole oikeuksia tehdä enempää pelaajatilejä!' no_perm: '&cEi oikeuksia' error: '&fVirhe: Ota yhteys palveluntarjoojaan!' -unsafe_spawn: '&fHengenvaarallinen poistumispaikka! Siirsimme sinut spawnille!' kick_forvip: '&cVIP pelaaja liittyi täyteen palvelimeen!' # AntiBot @@ -82,7 +81,6 @@ email_added: '[AuthMe] Sähköposti lisätty!' email_confirm: '[AuthMe] Vahvistuta sähköposti!' email_changed: '[AuthMe] Sähköposti vaihdettu!' email_send: '[AuthMe] Palautus sähköposti lähetetty!' -# 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' diff --git a/src/main/resources/messages/messages_fr.yml b/src/main/resources/messages/messages_fr.yml index 453955ff3..517250ac4 100644 --- a/src/main/resources/messages/messages_fr.yml +++ b/src/main/resources/messages/messages_fr.yml @@ -35,7 +35,6 @@ tempban_max_logins: '&cVous êtes temporairement banni suite à plusieurs échec max_reg: 'Vous avez atteint la limite d''inscription! &o(%reg_count/%max_acc : %reg_names)' no_perm: '&cVous n''êtes pas autorisé à utiliser cette commande.' error: '&cUne erreur est apparue, veuillez contacter un administrateur.' -unsafe_spawn: 'Téléportation dans un endroit sûr.' kick_forvip: 'Un joueur VIP a rejoint le serveur à votre place (serveur plein).' # AntiBot @@ -87,7 +86,6 @@ email_added: '&aEmail enregistré. En cas de perte de MDP, faites "/email recove 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 !' diff --git a/src/main/resources/messages/messages_gl.yml b/src/main/resources/messages/messages_gl.yml index b7575a98f..cdf5ddbe4 100644 --- a/src/main/resources/messages/messages_gl.yml +++ b/src/main/resources/messages/messages_gl.yml @@ -31,7 +31,6 @@ not_logged_in: '&cNon te identificaches!' max_reg: '&fExcediches o máximo de rexistros para a túa Conta' no_perm: '&cNon tes o permiso' error: '&fOcurriu un erro; contacta cun administrador' -unsafe_spawn: '&fA localización dende a que saíches era insegura, teletransportándote ao spawn do mundo' kick_forvip: '&cUn xogador VIP uniuse ao servidor cheo!' # AntiBot @@ -82,7 +81,6 @@ email_added: '[AuthMe] Correo engadido!' email_confirm: '[AuthMe] Confirma o teu correo!' email_changed: '[AuthMe] Cambiouse o correo!' email_send: '[AuthMe] Enviouse o correo de confirmación!' -# 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' diff --git a/src/main/resources/messages/messages_hu.yml b/src/main/resources/messages/messages_hu.yml index 5a6f5df2c..bd3579872 100644 --- a/src/main/resources/messages/messages_hu.yml +++ b/src/main/resources/messages/messages_hu.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cIdeiglenesen ki lettél tiltva mert túl sok alkalommal r max_reg: '&cElérted a maximálisan beregisztrálható karakterek számát. (%reg_count/%max_acc %reg_names)!' no_perm: '&cNincs jogod ehhez!' error: 'Hiba lépett fel! Lépj kapcsolatba a szerver tulajával sürgősen!' -unsafe_spawn: 'A kilépési helyzeted nem biztonságos, ezért elteleportálunk a kezdő pozícióra.' kick_forvip: '&3VIP játékos csatlakozott a szerverhez!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Az email címed rögzítése sikeresen megtörtént!' email_confirm: '&cKérlek ellenőrízd az email címedet!' email_changed: '&2Az email cím cseréje sikeresen megtörtént!' email_send: '&2A jelszó visszaállításhoz szükséges emailt elküldtük! Ellenőrízd a leveleidet!' -email_exists: '&cA visszaállító emailt elküldtük! Hiba esetén újra kérheted az alábbi parancs segítségével:' email_show: '&2A jelenlegi email-ed a következő: &f%email' incomplete_email_settings: 'Hiba: nem lett beállítva az össze szükséges beállítás az email küldéshez. Vedd fel a kapcsolatot egy adminnal.' email_already_used: '&4Ez az email cím már használatban van!' diff --git a/src/main/resources/messages/messages_id.yml b/src/main/resources/messages/messages_id.yml index ec289d8fe..79472486a 100644 --- a/src/main/resources/messages/messages_id.yml +++ b/src/main/resources/messages/messages_id.yml @@ -31,7 +31,6 @@ not_logged_in: '&cKamu belum login!' max_reg: '&Kamu telah mencapai batas maksimum pendaftaran di server ini!' no_perm: '&4Kamu tidak mempunyai izin melakukan ini!' error: '&4Terjadi kesalahan tak dikenal, silahkan hubungi Administrator!' -unsafe_spawn: '&cLokasi quit kamu tidak aman, kamu telah diteleport ke titik spawn world.' kick_forvip: '&3Player VIP mencoba masuk pada saat server sedang penuh!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Berhasil menambahkan alamat email ke akunmu!' email_confirm: '&cSilahkan konfirmasi alamat email kamu!' email_changed: '&2Alamat email telah diubah dengan benar!' email_send: '&2Email pemulihan akun telah dikirim! Silahkan periksa kotak masuk emailmu!' -email_exists: '&cEmail pemulihan sudah dikirim! kamu bisa membatalkan dan mengirimkan yg baru dengan command dibawah:' # 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' diff --git a/src/main/resources/messages/messages_it.yml b/src/main/resources/messages/messages_it.yml index 0f03ca65d..a4654309f 100644 --- a/src/main/resources/messages/messages_it.yml +++ b/src/main/resources/messages/messages_it.yml @@ -32,7 +32,6 @@ tempban_max_logins: '&cSei stato temporaneamente bandito per aver fallito l''aut max_reg: '&cHai raggiunto il numero massimo di registrazioni (%reg_count/%max_acc %reg_names) per questo indirizzo IP!' no_perm: '&4Non hai il permesso di eseguire questa operazione.' error: '&4Qualcosa è andato storto, riporta questo errore ad un amministratore!' -unsafe_spawn: '&cIl tuo punto di disconnessione risulta ostruito o insicuro, sei stato teletrasportato al punto di rigenerazione!' kick_forvip: '&3Un utente VIP è entrato mentre il server era pieno e ha preso il tuo posto!' # AntiBot @@ -84,7 +83,6 @@ 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 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' diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml index 98f493b79..b96c623dd 100644 --- a/src/main/resources/messages/messages_ko.yml +++ b/src/main/resources/messages/messages_ko.yml @@ -35,7 +35,6 @@ not_logged_in: '&c접속되어있지 않습니다!' max_reg: '&f당신은 가입할 수 있는 계정의 최대 한도를 초과했습니다' no_perm: '&c권한이 없습니다' error: '&f오류가 발생했습니다; 관리자에게 문의해주세요' -unsafe_spawn: '&f당신이 종료한 위치는 안전하지 않았습니다, 세계의 소환지점으로 이동합니다' kick_forvip: '&c서버가 만원인 상태일때 VIP 플레이어들만 입장이 가능합니다!' # AntiBot @@ -86,7 +85,6 @@ email_added: '[AuthMe] 이메일을 추가했습니다!' email_confirm: '[AuthMe] 당신의 이메일을 확인하세요!' email_changed: '[AuthMe] 이메일이 변경되었습니다!' email_send: '[AuthMe] 복구 이메일을 보냈습니다!' -email_exists: '[AuthMe] 당신의 계정에 이미 이메일이 존재합니다. 아래의 명령어를 통해 이메일을 변경하실 수 있습니다' # 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' diff --git a/src/main/resources/messages/messages_lt.yml b/src/main/resources/messages/messages_lt.yml index 324fc29a2..297510879 100644 --- a/src/main/resources/messages/messages_lt.yml +++ b/src/main/resources/messages/messages_lt.yml @@ -31,7 +31,6 @@ not_logged_in: '&cTu neprisijunges!' max_reg: '&cJus pasiekete maksimalu registraciju skaiciu.' no_perm: '&cNera leidimo' error: '&cAtsirado klaida, praneskite adminstratoriui.' -unsafe_spawn: '&6Atsijungimo vieta nesaugi, perkeliame jus i atsiradimo vieta.' kick_forvip: '&cA VIP prisijunge i pilna serveri!' # AntiBot @@ -82,7 +81,6 @@ same_nick: '&cKazkas situo vardu jau zaidzia.' # TODO email_confirm: '&cPlease confirm your email address!' # TODO email_changed: '&2Email address changed correctly!' # TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' -# 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' diff --git a/src/main/resources/messages/messages_nl.yml b/src/main/resources/messages/messages_nl.yml index 3ca9d325c..3804681d6 100644 --- a/src/main/resources/messages/messages_nl.yml +++ b/src/main/resources/messages/messages_nl.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cJe bent tijdelijk gebanned omdat het inloggen te vaak mis max_reg: 'Je hebt het maximum aantal registraties overschreden (%reg_count/%max_acc: %reg_names).' no_perm: '&cJe hebt geen rechten om deze actie uit te voeren!' error: 'Er is een onverwachte fout opgetreden, neem contact op met een administrator!' -unsafe_spawn: '&cDe locatie waar je de vorige keer het spel verliet was gevaarlijk, je bent geteleporteerd naar de spawn.' kick_forvip: '&cEen VIP-gebruiker heeft ingelogd toen de server vol was!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Het E-mailadres is succesvol toegevoegd aan je account!' email_confirm: '&cVerifiëer je E-mailadres alsjeblieft!' email_changed: '&2Het E-mailadres is succesvol veranderd!' email_send: '&2Een herstel E-mail is verzonden! Check alsjeblieft je mailbox!' -email_exists: '&cEen herstel E-mail voor je wachtwoord is al verzonden! Je kunt een nieuwe laten sturen met het volgende commando:' email_show: '&2Jouw huidige E-mailadres is: %email' incomplete_email_settings: 'Fout; er moeten nog enkele opties ingevuld worden om mails te kunnen sturen. Neem contact op met een administrator.' email_already_used: '&4Dit E-mailadres wordt al gebruikt' diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml index 9530ae06b..0b50688b4 100644 --- a/src/main/resources/messages/messages_pl.yml +++ b/src/main/resources/messages/messages_pl.yml @@ -31,7 +31,6 @@ tempban_max_logins: '&cZostales tymczasowo zbanowany za duza liczbe nieudanych l max_reg: '&fPrzekroczyles limit zarejestrowanych kont na serwerze.' no_perm: '&4Nie masz uprawnien' error: '&fBlad prosimy napisac do aministracji' -unsafe_spawn: '&fTwoje pozycja jest niebezpieczna. Zostaniesz przeniesiony na bezpieczny spawn.' kick_forvip: '&cA Gracz VIP dolaczyl do gry!' # AntiBot @@ -83,7 +82,6 @@ email_added: '[AuthMe] Email dodany!' email_confirm: '[AuthMe] Potwierdz swoj email!' email_changed: '[AuthMe] Email zmieniony!' email_send: '[AuthMe] Email z odzyskaniem wyslany!' -email_exists: '&cEmail z haslem zostal wyslany, aby wyslac go ponownie uzyj:' email_show: '&2Twoj aktualny adres email to: &f%email' incomplete_email_settings: 'Error: Nie wszystkie opcje odpowiedzialne za wysylanie emaili zostaly ustawione. Skontaktuj sie z administracja.' email_already_used: '&4Ten adres email jest aktualnie uzywany!' diff --git a/src/main/resources/messages/messages_pt.yml b/src/main/resources/messages/messages_pt.yml index 4e9d56232..ed6070246 100644 --- a/src/main/resources/messages/messages_pt.yml +++ b/src/main/resources/messages/messages_pt.yml @@ -31,7 +31,6 @@ tempban_max_logins: '&cVocê foi temporariamente banido por falhar muitas vezes 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 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!' # AntiBot @@ -83,7 +82,6 @@ email_added: 'Email adicionado com sucesso!' 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:' 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' diff --git a/src/main/resources/messages/messages_ro.yml b/src/main/resources/messages/messages_ro.yml index e87a68de4..03817b8f6 100644 --- a/src/main/resources/messages/messages_ro.yml +++ b/src/main/resources/messages/messages_ro.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cAi fost interzis temporar deoarece ai incercat sa te aute max_reg: '&cTe-ai inregistrat cu prea multe counturi (%reg_count/%max_acc %reg_names) pentru conexiunea ta!' no_perm: '&4Nu ai permisiunea!' error: '&4A aparut o eroare, te rugam contacteaza un membru din staff!' -unsafe_spawn: '&cLocatia in care esti acum nu este sigura, ai fost teleportat la spawn.' kick_forvip: '&3Un V.I.P a intrat pe server cand era plin!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Email-ul a fost adaugat cu succes la contul tau!' email_confirm: '&cTe rugam confirma adresa de email!' email_changed: '&2Email-ul a fost schimbat corect!' email_send: ' &2Recuperarea email-ul a fost trimis cu succes! Te rugam sa verificati email-ul in inbox!' -email_exists: '&cRecuperarea email-ului a fost deja trimisa! Puteti sa-l anulati si sa trimiteti unul nou folosind comanda de mai jos:' email_show: '&2Adresa ta curenta de email este: &f%email' incomplete_email_settings: 'Eroare: nu toate setarile necesare pentru trimiterea email-ului sunt facute! Te rugam contacteaza un administrator.' email_already_used: '&4Email-ul a fost deja folosit' diff --git a/src/main/resources/messages/messages_ru.yml b/src/main/resources/messages/messages_ru.yml index 56cca9748..5b7d15caf 100644 --- a/src/main/resources/messages/messages_ru.yml +++ b/src/main/resources/messages/messages_ru.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cВы были временно забанены, пото max_reg: '&cВы превысили максимальное количество регистраций на сервере! (%reg_count/%max_acc %reg_names)' no_perm: '&cНедостаточно прав' error: '&cПроизошла ошибка. Свяжитесь с администратором' -unsafe_spawn: '&eВаше расположение перед выходом было опасным - вы перенесены на спавн' kick_forvip: '&6VIP игрок зашел на переполненный сервер!' # АнтиБот (Защита от ботов) @@ -82,7 +81,6 @@ email_added: '[AuthMe] Email добавлен!' email_confirm: '[AuthMe] Подтвердите ваш Email!' email_changed: '[AuthMe] Email изменен!' email_send: '[AuthMe] Письмо с инструкциями для восстановления было отправлено на ваш Email!' -email_exists: '&cВосстановительное письмо через email отправлено! Вы можете отменить его командой ниже:' email_show: '&2Ваш текущий адрес электронной почты: &f%email' incomplete_email_settings: 'Ошибка: не все необходимые параметры установлены для отправки электронной почты. Пожалуйста, обратитесь к администратору.' email_already_used: '&4Этот email уже используется.' diff --git a/src/main/resources/messages/messages_sk.yml b/src/main/resources/messages/messages_sk.yml index 47ae3b5c1..2f3f1c7b7 100644 --- a/src/main/resources/messages/messages_sk.yml +++ b/src/main/resources/messages/messages_sk.yml @@ -35,7 +35,6 @@ not_logged_in: '&cNie si este prihláseny!' max_reg: '&fDosiahol si maximum registrovanych uctov.' no_perm: '&cZiadne' error: '&fNastala chyba; Kontaktujte administrátora' -unsafe_spawn: '&fTvoj pozícia bol nebezpecná, teleportujem hraca na spawn' # TODO kick_forvip: '&3A VIP player has joined the server when it was full!' # AntiBot @@ -86,7 +85,6 @@ same_nick: '&fHrác s tymto nickom uz hrá!' # TODO email_confirm: '&cPlease confirm your email address!' # TODO email_changed: '&2Email address changed correctly!' # TODO email_send: '&2Recovery email sent successfully! Please check your email inbox!' -# 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' diff --git a/src/main/resources/messages/messages_tr.yml b/src/main/resources/messages/messages_tr.yml index 6f9500db3..2997ddd5b 100644 --- a/src/main/resources/messages/messages_tr.yml +++ b/src/main/resources/messages/messages_tr.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cBir cok kez yanlis giris yaptiginiz icin gecici olarak ba 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!' -unsafe_spawn: '&cOyundan ciktigin yer guvenli degil, baslangic noktasina isinlaniyorsun.' kick_forvip: '&3Bir VIP oyuna giris yaptigi icin atildin!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Eposta basariyla kullaniciniza eklendi!' 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:' 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.' diff --git a/src/main/resources/messages/messages_uk.yml b/src/main/resources/messages/messages_uk.yml index c14477011..fdd80bb47 100644 --- a/src/main/resources/messages/messages_uk.yml +++ b/src/main/resources/messages/messages_uk.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cВаш IP тимчасово заблоковано, із max_reg: '&cВичерпано ліміт реєстрацій (%reg_count/%max_acc %reg_names) для вашого підключення!' no_perm: '&4У вас недостатньо прав, щоб застосувати цю команду!' error: '&4[AuthMe] Error. Будь ласка, повідомте адміністратора!' -unsafe_spawn: '&cВаше останнє місцезнаходження не є безпечним. Вас було переміщено на точку спавна.' kick_forvip: '&3Вас кікнуто, внаслідок того, що VIP гравець зайшов на сервер коли небуло вільних місць.' # AntiBot @@ -81,7 +80,6 @@ email_added: '&2Електронну пошту успішно прив’яза email_confirm: '&cАдреси не співпадають.' email_changed: '&2E-mail успішно змінено.' email_send: '&2Лист для відновлення доступу надіслано. Будь ласка, провірте свою пошту!' -email_exists: '&cЛист для відновлення доступу вже було надіслано! Ви можете деактуалізувати його, використавши наступну команду:' # TODO email_show: '&2Your current email address is: &f%email' incomplete_email_settings: '&4[AuthMe] Error: Не всі необхідні налаштування є встановленими, щоб надсилати електронну пошту. Будь ласка, повідомте адміністратора!' email_already_used: '&4До цієї електронної пошти прив’язано забагато акаунтів!' diff --git a/src/main/resources/messages/messages_vn.yml b/src/main/resources/messages/messages_vn.yml index 2e397448d..9c08e4b8b 100644 --- a/src/main/resources/messages/messages_vn.yml +++ b/src/main/resources/messages/messages_vn.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&cBạn đã bị tạm thời khóa truy cập do đăng n max_reg: '&cBạn đã vượt quá giới hạn tối đa đăng ký tài khoản (%reg_count/%max_acc %reg_names) cho những lần kết nối tài khoản!' no_perm: '&4Bạn không có quyền truy cập lệnh này!' error: '&4Lỗi! Vui lòng liên hệ quản trị viên hoặc admin' -unsafe_spawn: '&cBạn đang ở vị trí không an toàn, bạn đã được dịch chuyển về Spawn.' kick_forvip: '&eChỉ có thành viên VIP mới được tham gia khi máy chủ đầy!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2Địa chỉ email đã thêm vào tài khoản của bạn thà email_confirm: '&cVui lòng xác nhận địa chỉ email của bạn!' email_changed: '&2Địa chỉ email đã thay đổi!' email_send: '&2Email phục hồi đã được gửi thành công! Vui lòng kiểm tra hộp thư đến trong email của bạn.' -email_exists: '&cEmail phục hồi đã được gửi, bạn có thể hủy bỏ và gửi thư mới bằng cách sử dụng lệnh sau:' email_show: '&2Địa chỉ email hiện tại của bạn là: &f%email' incomplete_email_settings: 'Lỗi: các thiết lập để gửi thư không được cài đặt đầy đủ. Vui lòng liên hệ với quản trị viên để thông báo lỗi.' email_already_used: '&4Địa chỉ email đã được sử dụng' diff --git a/src/main/resources/messages/messages_zhcn.yml b/src/main/resources/messages/messages_zhcn.yml index 19a14d347..541a239ee 100644 --- a/src/main/resources/messages/messages_zhcn.yml +++ b/src/main/resources/messages/messages_zhcn.yml @@ -31,7 +31,6 @@ tempban_max_logins: '&c由于您登录失败次数过多,已被暂时禁止登 max_reg: '&8[&6玩家系统&8] &f你不允许再为你的IP在服务器注册更多用户了!' no_perm: '&8[&6玩家系统&8] &c没有权限' error: '&8[&6玩家系统&8] &f发现错误,请联系管理员' -unsafe_spawn: '&8[&6玩家系统&8] &f你退出服务器时的位置不安全,正在传送你到此世界的出生点' kick_forvip: '&8[&6玩家系统&8] &cA VIP玩家加入了已满的服务器!' # AntiBot @@ -83,7 +82,6 @@ email_added: '&8[&6玩家系统&8] &f邮箱已添加 !' email_confirm: '&8[&6玩家系统&8] &f确认你的邮箱 !' email_changed: '&8[&6玩家系统&8] &f邮箱已改变 !' email_send: '&8[&6玩家系统&8] &f恢复邮件已发送 !' -email_exists: '&8[&6玩家系统&8] &c恢复邮件已发送 ! 你可以丢弃它然後使用以下的指令来发送新的邮件:' email_show: '&2您当前的电子邮件地址为: &f%email' incomplete_email_settings: '错误:并非所有发送邮件需要的设置都已被设置,请联系管理员' email_already_used: '&8[&6玩家系统&8] &4邮箱已被使用' diff --git a/src/main/resources/messages/messages_zhhk.yml b/src/main/resources/messages/messages_zhhk.yml index 342893e34..ee0eadb01 100644 --- a/src/main/resources/messages/messages_zhhk.yml +++ b/src/main/resources/messages/messages_zhhk.yml @@ -35,7 +35,6 @@ not_logged_in: '&8[&6用戶系統&8] &c你還沒有登入 !' max_reg: '&8[&6用戶系統&8] &f你的IP地址已達到註冊數上限。' no_perm: '&8[&6用戶系統&8] &b嗯~你想幹甚麼?' error: '&8[&6用戶系統&8] &f發生錯誤,請與管理員聯絡。' -unsafe_spawn: '&8[&6用戶系統&8] &f你的登出位置不安全,現在將傳送你到重生點。' kick_forvip: '&c喔!因為有VIP玩家登入了伺服器。' # AntiBot @@ -86,7 +85,6 @@ email_added: '&8[&6用戶系統&8] &a已新增你的電郵地址。' email_confirm: '&8[&6用戶系統&8] &5請重覆輸入你的電郵地址。' email_changed: '&8[&6用戶系統&8] &a你的電郵地址已更改。' email_send: '&8[&6用戶系統&8] &a忘記密碼信件已寄出,請查收。' -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_already_used: '&8[&6用戶系統&8] &4這個電郵地址已被使用。' diff --git a/src/main/resources/messages/messages_zhmc.yml b/src/main/resources/messages/messages_zhmc.yml index 25b29a87c..c4d8897eb 100644 --- a/src/main/resources/messages/messages_zhmc.yml +++ b/src/main/resources/messages/messages_zhmc.yml @@ -30,7 +30,6 @@ tempban_max_logins: '&c由於登錄失敗次數過多,您已被暫時禁止。 max_reg: '&c您已超過註冊的最大數量(%reg_count/%max_acc %reg_names)!' no_perm: '&4您沒有執行此操作的權限!' error: '&4發生錯誤!請聯繫伺服器管理員!' -unsafe_spawn: '&c你的登出地點並不安全,你已經被傳送到世界的重生點。' kick_forvip: '&3一名VIP玩家在服務器已滿時已加入伺服器!' # AntiBot @@ -82,7 +81,6 @@ email_added: '&2電子郵件地址已成功添加到您的帳戶!' email_confirm: '&c請確認你的電郵地址!' email_changed: '&2已正確地更改電子郵件地址!' email_send: '&2帳戶恢復電子郵件已成功發送! 請檢查您的電子郵件收件箱!' -email_exists: '&c備援電子郵件已傳送! 您可以使用以下命令丟棄它並發送一個新的備援電子郵件:' # TODO email_show: '&2Your current email address is: &f%email' incomplete_email_settings: '缺少必要的配置來為發送電子郵件。請聯繫管理員。' email_already_used: '&4此電子郵件地址已被使用' diff --git a/src/main/resources/messages/messages_zhtw.yml b/src/main/resources/messages/messages_zhtw.yml index 0d40b92da..747357678 100644 --- a/src/main/resources/messages/messages_zhtw.yml +++ b/src/main/resources/messages/messages_zhtw.yml @@ -35,7 +35,6 @@ not_logged_in: '&b【AuthMe】&6你還沒有登入!' max_reg: '&b【AuthMe】&6你的 IP 位置所註冊的帳號數量已經達到最大。' no_perm: '&b【AuthMe】&6你沒有使用該指令的權限。' error: '&b【AuthMe】&6發生錯誤,請聯繫管理員' -unsafe_spawn: '&b【AuthMe】&6你登出的地點不安全,已傳送你到安全的地點。' kick_forvip: '&b【AuthMe】&6你已經被請出。&c原因 : 有 VIP 玩家登入伺服器' # AntiBot @@ -86,7 +85,6 @@ email_added: '&b【AuthMe】&6已添加Email!' email_confirm: '&b【AuthMe】&6請驗證你的Email!' email_changed: '&b【AuthMe】&6Email已變更!' email_send: '&b【AuthMe】&6已經送出重設密碼要求至你的Email , 請查收。' -email_exists: '&b【AuthMe】&6這個帳戶已經有設定電子郵件了' # 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_already_used: '&b【AuthMe】&4這個電郵地址已被使用。' diff --git a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java index e646031f8..d4560640b 100644 --- a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java @@ -4,6 +4,7 @@ import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import fr.xephi.authme.service.CommonService; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Test; import org.junit.runner.RunWith; @@ -13,6 +14,8 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Collections; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -71,4 +74,17 @@ public class UnregisterCommandTest { verify(management).performUnregister(player, password); } + @Test + public void shouldStopIfSenderIsNotPlayer() { + // given + CommandSender sender = mock(CommandSender.class); + + // when + command.executeCommand(sender, Collections.singletonList("password")); + + // then + verifyZeroInteractions(playerCache, management); + verify(sender).sendMessage(argThat(containsString("/authme unregister "))); + } + } diff --git a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java index 8f48fffec..79e873176 100644 --- a/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/message/MessagesIntegrationTest.java @@ -89,7 +89,7 @@ public class MessagesIntegrationTest { @Test public void shouldFormatColorCodes() { // given - MessageKey key = MessageKey.UNSAFE_QUIT_LOCATION; + MessageKey key = MessageKey.LOGIN_SUCCESS; // when String[] message = messages.retrieve(key); @@ -115,7 +115,7 @@ public class MessagesIntegrationTest { @Test public void shouldSendMessageToPlayer() { // given - MessageKey key = MessageKey.UNSAFE_QUIT_LOCATION; + MessageKey key = MessageKey.LOGIN_SUCCESS; Player player = Mockito.mock(Player.class); // when diff --git a/src/test/java/tools/messages/CheckMessageKeyUsages.java b/src/test/java/tools/messages/CheckMessageKeyUsages.java new file mode 100644 index 000000000..51b8eb12b --- /dev/null +++ b/src/test/java/tools/messages/CheckMessageKeyUsages.java @@ -0,0 +1,91 @@ +package tools.messages; + +import com.google.common.collect.Lists; +import fr.xephi.authme.message.MessageKey; +import tools.utils.FileIoUtils; +import tools.utils.ToolTask; +import tools.utils.ToolsConstants; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; + +/** + * Task which checks for {@link MessageKey} usages. + */ +public class CheckMessageKeyUsages implements ToolTask { + + private static final Predicate SHOULD_CHECK_FILE = + file -> file.getName().endsWith(".java") && !file.getName().endsWith("MessageKey.java"); + + @Override + public String getTaskName() { + return "checkMessageUses"; + } + + @Override + public void execute(Scanner scanner) { + System.out.println("Enter a message key to find the files where it is used"); + System.out.println("Enter empty line to search for all unused message keys"); + String key = scanner.nextLine(); + + if (key.trim().isEmpty()) { + List unusedKeys = findUnusedKeys(); + if (unusedKeys.isEmpty()) { + System.out.println("No unused MessageKey entries found :)"); + } else { + System.out.println("Did not find usages for keys:\n- " + + String.join("\n- ", Lists.transform(unusedKeys, MessageKey::name))); + } + } else { + MessageKey messageKey = MessageKey.valueOf(key); + List filesUsingKey = findUsagesOfKey(messageKey); + System.out.println("The following files use '" + key + "':\n- " + + filesUsingKey.stream().map(File::getName).collect(Collectors.joining("\n- "))); + } + } + + private List findUnusedKeys() { + List keys = new ArrayList<>(asList(MessageKey.values())); + File sourceFolder = new File(ToolsConstants.MAIN_SOURCE_ROOT); + + Consumer fileProcessor = file -> { + String source = FileIoUtils.readFromFile(file.toPath()); + keys.removeIf(key -> source.contains(key.name())); + }; + + walkJavaFileTree(sourceFolder, fileProcessor); + return keys; + } + + private List findUsagesOfKey(MessageKey key) { + List filesUsingKey = new ArrayList<>(); + File sourceFolder = new File(ToolsConstants.MAIN_SOURCE_ROOT); + + Consumer usagesCollector = file -> { + String source = FileIoUtils.readFromFile(file.toPath()); + if (source.contains(key.name())) { + filesUsingKey.add(file); + } + }; + + walkJavaFileTree(sourceFolder, usagesCollector); + return filesUsingKey; + } + + private static void walkJavaFileTree(File folder, Consumer javaFileConsumer) { + for (File file : FileIoUtils.listFilesOrThrow(folder)) { + if (file.isDirectory()) { + walkJavaFileTree(file, javaFileConsumer); + } else if (file.isFile() && SHOULD_CHECK_FILE.test(file)) { + javaFileConsumer.accept(file); + } + } + } +} diff --git a/src/test/resources/fr/xephi/authme/message/messages_test.yml b/src/test/resources/fr/xephi/authme/message/messages_test.yml index 3c03dd1c4..d955a6a64 100644 --- a/src/test/resources/fr/xephi/authme/message/messages_test.yml +++ b/src/test/resources/fr/xephi/authme/message/messages_test.yml @@ -1,7 +1,7 @@ # Sample messages file unknown_user: 'We''ve got%nl%new lines%nl%and '' apostrophes' -unsafe_spawn: '&cHere we have&bdefined some colors &dand some other <hings' +login: '&cHere we have&bdefined some colors &dand some other <hings' reg_voluntarily: 'You can register yourself to the server with the command "/register "' usage_log: '&cUsage: /login ' wrong_pwd: '&cWrong password!' diff --git a/src/test/resources/fr/xephi/authme/message/messages_test2.yml b/src/test/resources/fr/xephi/authme/message/messages_test2.yml index bdbedb8ae..e4a607239 100644 --- a/src/test/resources/fr/xephi/authme/message/messages_test2.yml +++ b/src/test/resources/fr/xephi/authme/message/messages_test2.yml @@ -1,6 +1,6 @@ # Sample messages file unknown_user: 'Message from test2' -unsafe_spawn: 'test2 - unsafe spawn' +login: 'test2 - login' not_logged_in: 'test2 - not logged in' wrong_pwd: 'test2 - wrong password' From b7901c6b59abc8c52fa343d54f5fec62445029ba Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 24 Mar 2017 23:23:40 +0100 Subject: [PATCH 116/125] Simplify exclusion of enum properties in SettingsConsistencyTest - Allow enum values to be absent if they are deprecated --- .../settings/SettingsConsistencyTest.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java index c3648a7f1..87d9b8aac 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsConsistencyTest.java @@ -5,14 +5,11 @@ import ch.jalu.configme.SettingsHolder; 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 com.google.common.collect.ImmutableSet; import fr.xephi.authme.ClassCollector; import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; -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; @@ -42,15 +39,10 @@ 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. + * Properties to exclude from the enum check. */ - 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 final Set> EXCLUDED_ENUM_PROPERTIES = + ImmutableSet.of(SecuritySettings.PASSWORD_HASH, SecuritySettings.LEGACY_HASHES); private static ConfigurationData configurationData; @@ -165,10 +157,10 @@ public class SettingsConsistencyTest { for (Property property : configurationData.getProperties()) { // when Class> enumClass = getEnumClass(property); - if (enumClass != null) { + if (enumClass != null && !EXCLUDED_ENUM_PROPERTIES.contains(property)) { String comments = String.join("\n", configurationData.getCommentsForSection(property.getPath())); Arrays.stream(enumClass.getEnumConstants()) - .filter(e -> !comments.contains(e.name()) && !isExcluded(property, e)) + .filter(e -> !comments.contains(e.name()) && !isDeprecated(e)) .findFirst() .ifPresent(e -> invalidEnumProperties.put(property, e)); } @@ -199,16 +191,12 @@ public class SettingsConsistencyTest { 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 + private static boolean isDeprecated(Enum enumValue) { + try { + return enumValue.getDeclaringClass().getField(enumValue.name()).isAnnotationPresent(Deprecated.class); + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Could not fetch field for enum '" + enumValue + + "' in " + enumValue.getDeclaringClass()); + } } } From c54231b2554c1df6da7290f41f6b3ae3d1303ac1 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 25 Mar 2017 00:23:54 +0100 Subject: [PATCH 117/125] #1138 Show warning for hashes that will be deprecated in 5.4 - Introduce Usage.DEPRECATED to mark the hash algorithms accordingly - Log warning when such a deprecated hash algorithm is used - Update hash algorithms doc page --- docs/hash_algorithms.md | 14 ++-- src/main/java/fr/xephi/authme/AuthMe.java | 22 ++++-- .../authme/initialization/OnStartupTasks.java | 22 ++++++ .../authme/security/crypts/DoubleMd5.java | 4 + .../fr/xephi/authme/security/crypts/Md5.java | 3 + .../authme/security/crypts/PlainText.java | 4 + .../fr/xephi/authme/security/crypts/Sha1.java | 3 + .../xephi/authme/security/crypts/Sha512.java | 3 + .../authme/security/crypts/Whirlpool.java | 4 + .../security/crypts/description/Usage.java | 3 + .../initialization/OnStartupTasksTest.java | 76 +++++++++++++++++++ 11 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java diff --git a/docs/hash_algorithms.md b/docs/hash_algorithms.md index 02bcafe6f..8e066dc7f 100644 --- a/docs/hash_algorithms.md +++ b/docs/hash_algorithms.md @@ -1,5 +1,5 @@ - + ## Hash Algorithms AuthMe supports the following hash algorithms for storing your passwords safely. @@ -10,11 +10,11 @@ Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Se BCRYPT | Recommended | 60 | | | Text | | BCRYPT2Y | Recommended | 60 | | | Text | 22 | CRAZYCRYPT1 | Do not use | 128 | | | Username | | -DOUBLEMD5 | Do not use | 32 | | | None | | +DOUBLEMD5 | Deprecated | 32 | | | None | | IPB3 | Acceptable | 32 | | | Text | 5 | Y IPB4 | Does not work | 60 | | | Text | 22 | Y JOOMLA | Acceptable | 65 | | | Text | 32 | -MD5 | Do not use | 32 | | | None | | +MD5 | Deprecated | 32 | | | None | | MD5VB | Acceptable | 56 | | | Text | 16 | MYBB | Acceptable | 32 | | | Text | 8 | Y PBKDF2 | Recommended | 165 | | | Text | 16 | @@ -24,14 +24,14 @@ PHPFUSION | Do not use | 64 | Y | | | | Y ROYALAUTH | Do not use | 128 | | | None | | SALTED2MD5 | Acceptable | 32 | | | Text | | Y SALTEDSHA512 | Recommended | 128 | | | | | Y -SHA1 | Do not use | 40 | | | None | | +SHA1 | Deprecated | 40 | | | None | | SHA256 | Recommended | 86 | | | Text | 16 | -SHA512 | Do not use | 128 | | | None | | +SHA512 | Deprecated | 128 | | | None | | SMF | Do not use | 40 | | | Username | | TWO_FACTOR | Does not work | 16 | | | None | | WBB3 | Acceptable | 40 | | | Text | 40 | Y WBB4 | Recommended | 60 | | | Text | 8 | -WHIRLPOOL | Do not use | 128 | | | None | | +WHIRLPOOL | Deprecated | 128 | | | None | | WORDPRESS | Acceptable | 34 | | | Text | 9 | XAUTH | Recommended | 140 | | | Text | 12 | XFBCRYPT | | 60 | | | | | @@ -82,4 +82,4 @@ or bad. --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Nov 25 15:48:35 CET 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Mar 25 00:15:27 CET 2017 diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index b66258cb2..00beaf51d 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -26,6 +26,7 @@ import fr.xephi.authme.listener.PlayerListener19; import fr.xephi.authme.listener.ServerListener; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsSystemType; +import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; @@ -148,7 +149,8 @@ public class AuthMe extends JavaPlugin { // If server is using PermissionsBukkit, print a warning that some features may not be supported if (PermissionsSystemType.PERMISSIONS_BUKKIT.equals(permsMan.getPermissionSystem())) { - ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions features may not be supported!"); + ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions " + + "features may not be supported!"); } // Do a backup on start @@ -159,10 +161,12 @@ public class AuthMe extends JavaPlugin { // Sponsor messages ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt."); - ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!"); + ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader " + + "in Italy as Game Server Provider!"); // Successful message - ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber() + " correctly enabled!"); + ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber() + + " correctly enabled!"); // Purge on start if enabled PurgeService purgeService = injector.getSingleton(PurgeService.class); @@ -248,7 +252,7 @@ public class AuthMe extends JavaPlugin { * * @param injector the injector */ - protected void instantiateServices(Injector injector) { + void instantiateServices(Injector injector) { // PlayerCache is still injected statically sometimes PlayerCache playerCache = PlayerCache.getInstance(); injector.register(PlayerCache.class, playerCache); @@ -283,6 +287,14 @@ public class AuthMe extends JavaPlugin { && settings.getProperty(EmailSettings.SMTP_PORT) != 25) { ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25"); } + + // Unsalted hashes will be deprecated in 5.4 (see Github issue #1016). Exclude RoyalAuth from this check because + // it is needed to hook into an existing system. + HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH); + if (OnStartupTasks.isHashDeprecatedIn54(hash)) { + ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed " + + "in 5.4 -- do you still need it? Comment on https://github.com/Xephi/AuthMeReloaded/issues/1016"); + } } /** @@ -290,7 +302,7 @@ public class AuthMe extends JavaPlugin { * * @param injector the injector */ - protected void registerEventListeners(Injector injector) { + void registerEventListeners(Injector injector) { // Get the plugin manager instance PluginManager pluginManager = getServer().getPluginManager(); diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java index 8767e88a6..bce15af47 100644 --- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java +++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java @@ -7,6 +7,9 @@ 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.security.HashAlgorithm; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import org.bstats.Metrics; import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.Log4JFilter; @@ -138,4 +141,23 @@ public class OnStartupTasks { } } } + + /** + * Returns whether the hash algorithm is deprecated and won't be able + * to be actively used anymore in 5.4. + * + * @param hash the hash algorithm to check + * @return true if the hash will be deprecated, false otherwise + * @see #1016 + */ + public static boolean isHashDeprecatedIn54(HashAlgorithm hash) { + if (hash.getClazz() == null || hash == HashAlgorithm.PLAINTEXT) { + // Exclude PLAINTEXT from this check because it already has a mandatory migration, which takes care of + // sending all the necessary messages and warnings. + return false; + } + + Recommendation recommendation = hash.getClazz().getAnnotation(Recommendation.class); + return recommendation != null && recommendation.value() == Usage.DEPRECATED; + } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java index c28e0440a..54158419b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java @@ -1,7 +1,11 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; + import static fr.xephi.authme.security.HashUtils.md5; +@Recommendation(Usage.DEPRECATED) public class DoubleMd5 extends UnsaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/Md5.java b/src/main/java/fr/xephi/authme/security/crypts/Md5.java index c2a2ba04a..a5f6d91d2 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Md5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Md5.java @@ -1,7 +1,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; +@Recommendation(Usage.DEPRECATED) public class Md5 extends UnsaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/PlainText.java b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java index add333d69..43d6e8200 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PlainText.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java @@ -1,11 +1,15 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; + /** * Plaintext password storage. * * @deprecated Using this is no longer supported. AuthMe will migrate to SHA256 on startup. */ @Deprecated +@Recommendation(Usage.DEPRECATED) public class PlainText extends UnsaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha1.java b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java index e3d078e7e..3430752b5 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Sha1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java @@ -1,7 +1,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; +@Recommendation(Usage.DEPRECATED) public class Sha1 extends UnsaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha512.java b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java index 12e51a315..4e9682ffe 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Sha512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java @@ -1,7 +1,10 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; +@Recommendation(Usage.DEPRECATED) public class Sha512 extends UnsaltedMethod { @Override diff --git a/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java index 84efae8e8..c1ea747a9 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java +++ b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java @@ -59,8 +59,12 @@ package fr.xephi.authme.security.crypts; * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; + import java.util.Arrays; +@Recommendation(Usage.DEPRECATED) public class Whirlpool extends UnsaltedMethod { /** diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java index c11c335ac..9ccc52c4f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java @@ -20,6 +20,9 @@ public enum Usage { /** Hash algorithm is not recommended to be used. Use only if required by another system. */ DO_NOT_USE, + /** Algorithm that is or will be no longer supported actively. */ + DEPRECATED, + /** The algorithm does not work properly; do not use. */ DOES_NOT_WORK diff --git a/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java b/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java new file mode 100644 index 000000000..4a95a1029 --- /dev/null +++ b/src/test/java/fr/xephi/authme/initialization/OnStartupTasksTest.java @@ -0,0 +1,76 @@ +package fr.xephi.authme.initialization; + +import ch.jalu.injector.exceptions.InjectorReflectionException; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.security.HashAlgorithm; +import org.junit.Test; + +import java.util.logging.Logger; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link OnStartupTasks}. + */ +public class OnStartupTasksTest { + + @Test + public void shouldDisplayLegacyJarHint() { + // given + Logger logger = TestHelper.setupLogger(); + NoClassDefFoundError noClassDefError = new NoClassDefFoundError("Lcom/google/gson/Gson;"); + ReflectiveOperationException ex2 = new ReflectiveOperationException("", noClassDefError); + InjectorReflectionException ex = new InjectorReflectionException("", ex2); + + // when + OnStartupTasks.displayLegacyJarHint(ex); + + // then + verify(logger).warning("YOU MUST DOWNLOAD THE LEGACY JAR TO USE AUTHME ON YOUR SERVER"); + } + + @Test + public void shouldNotDisplayLegacyHintForDifferentException() { + // given + Logger logger = TestHelper.setupLogger(); + NullPointerException npe = new NullPointerException(); + + // when + OnStartupTasks.displayLegacyJarHint(npe); + + // then + verifyZeroInteractions(logger); + } + + @Test + public void shouldNotDisplayLegacyHintForWrongCause() { + // given + Logger logger = TestHelper.setupLogger(); + IllegalAccessException illegalAccessException = new IllegalAccessException("Lcom/google/gson/Gson;"); + ReflectiveOperationException ex2 = new ReflectiveOperationException("", illegalAccessException); + InjectorReflectionException ex = new InjectorReflectionException("", ex2); + + // when + OnStartupTasks.displayLegacyJarHint(ex); + + // then + verifyZeroInteractions(logger); + } + + @Test + public void shouldCheckIfHashIsDeprecatedIn54() { + // given / when / then + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.CUSTOM), equalTo(false)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.IPB3), equalTo(false)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.PLAINTEXT), equalTo(false)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA256), equalTo(false)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WORDPRESS), equalTo(false)); + + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.MD5), equalTo(true)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.SHA512), equalTo(true)); + assertThat(OnStartupTasks.isHashDeprecatedIn54(HashAlgorithm.WHIRLPOOL), equalTo(true)); + } +} From 8cf7983027b48ee91bcef1feb2564d0929af2dba Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 26 Mar 2017 12:10:51 +0200 Subject: [PATCH 118/125] #1034 Add debug sections for spawn and input validation --- .checkstyle.xml | 1 + src/main/java/fr/xephi/authme/AuthMe.java | 3 +- .../executable/authme/debug/DebugCommand.java | 11 +- .../authme/debug/InputValidator.java | 115 ++++++++++++++++++ .../authme/debug/LimboPlayerViewer.java | 7 +- .../authme/debug/PermissionGroups.java | 1 + .../authme/debug/SpawnLocationViewer.java | 78 ++++++++++++ .../authme/debug/TestEmailSender.java | 3 +- .../xephi/authme/listener/OnJoinVerifier.java | 2 +- 9 files changed, 210 insertions(+), 11 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java diff --git a/.checkstyle.xml b/.checkstyle.xml index 00141bd6b..017242e36 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -177,6 +177,7 @@ + diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 00beaf51d..f74d56067 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -288,8 +288,7 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25"); } - // Unsalted hashes will be deprecated in 5.4 (see Github issue #1016). Exclude RoyalAuth from this check because - // it is needed to hook into an existing system. + // Unsalted hashes will be deprecated in 5.4 (see Github issue #1016) HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH); if (OnStartupTasks.isHashDeprecatedIn54(hash)) { ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed " 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 5cd7fb4f5..fc7cd4291 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 @@ -16,13 +16,14 @@ import java.util.TreeMap; */ public class DebugCommand implements ExecutableCommand { + private static final Set> SECTION_CLASSES = ImmutableSet.of( + PermissionGroups.class, DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, InputValidator.class, + LimboPlayerViewer.class, CountryLookup.class, HasPermissionChecker.class, TestEmailSender.class, + SpawnLocationViewer.class); + @Inject private Factory debugSectionFactory; - private Set> sectionClasses = ImmutableSet.of(PermissionGroups.class, - DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, LimboPlayerViewer.class, CountryLookup.class, - HasPermissionChecker.class, TestEmailSender.class); - private Map sections; @Override @@ -48,7 +49,7 @@ public class DebugCommand implements ExecutableCommand { private Map getSections() { if (sections == null) { Map sections = new TreeMap<>(); - for (Class sectionClass : sectionClasses) { + for (Class sectionClass : SECTION_CLASSES) { DebugSection section = debugSectionFactory.newInstance(sectionClass); sections.put(section.getName(), section); } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java new file mode 100644 index 000000000..472025060 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java @@ -0,0 +1,115 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.listener.FailedVerificationException; +import fr.xephi.authme.listener.OnJoinVerifier; +import fr.xephi.authme.message.Messages; +import fr.xephi.authme.service.ValidationService; +import fr.xephi.authme.service.ValidationService.ValidationResult; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.List; + +import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.MAIL; +import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.NAME; +import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.PASS; + +/** + * Checks if a sample username, email or password is valid according to the AuthMe settings. + */ +class InputValidator implements DebugSection { + + @Inject + private ValidationService validationService; + + @Inject + private Messages messages; + + @Inject + private OnJoinVerifier onJoinVerifier; + + + @Override + public String getName() { + return "valid"; + } + + @Override + public String getDescription() { + return "Check if email / password is valid according to your settings"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.size() < 2 || !ValidationObject.matchesAny(arguments.get(0))) { + displayUsageHint(sender); + + } else if (PASS.matches(arguments.get(0))) { + validatePassword(sender, arguments.get(1)); + + } else if (MAIL.matches(arguments.get(0))) { + validateEmail(sender, arguments.get(1)); + + } else if (NAME.matches(arguments.get(0))) { + validateUsername(sender, arguments.get(1)); + + } else { + throw new IllegalStateException("Unexpected validation object with arg[0] = '" + arguments.get(0) + "'"); + } + } + + private void displayUsageHint(CommandSender sender) { + sender.sendMessage("You can define forbidden emails and passwords in your config.yml"); + sender.sendMessage("This command allows you to test some of the values:"); + sender.sendMessage("/authme debug valid pass test1234 -- test if 'test1234' is allowed password"); + sender.sendMessage("/authme debug valid mail t@t.tld -- test if 't@t.tld' is allowed email"); + sender.sendMessage("/authme debug valid name bobby1 -- test if 'bobby1' is allowed username"); + } + + private void validatePassword(CommandSender sender, String password) { + ValidationResult validationResult = validationService.validatePassword(password, ""); + sender.sendMessage("Validation of password '" + password + "' returned:"); + if (validationResult.hasError()) { + messages.send(sender, validationResult.getMessageKey(), validationResult.getArgs()); + } else { + sender.sendMessage(ChatColor.DARK_GREEN + "Valid password!"); + } + } + + private void validateEmail(CommandSender sender, String email) { + boolean isValidEmail = validationService.validateEmail(email); + sender.sendMessage("Validation of email '" + email + "' returned:"); + if (isValidEmail) { + sender.sendMessage(ChatColor.DARK_GREEN + "Valid email!"); + } else { + sender.sendMessage(ChatColor.DARK_RED + "Email is not valid!"); + } + } + + private void validateUsername(CommandSender sender, String username) { + sender.sendMessage("Validation of username '" + username + "' returned:"); + try { + onJoinVerifier.checkIsValidName(username); + sender.sendMessage("Valid username!"); + } catch (FailedVerificationException failedVerificationEx) { + messages.send(sender, failedVerificationEx.getReason(), failedVerificationEx.getArgs()); + } + } + + + enum ValidationObject { + + PASS, MAIL, NAME; + + static boolean matchesAny(String arg) { + return Arrays.stream(values()).anyMatch(vo -> vo.matches(arg)); + } + + boolean matches(String arg) { + return name().equalsIgnoreCase(arg); + } + } + +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java index bf7f9b3c7..a8c62e683 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -3,6 +3,7 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.data.limbo.persistence.LimboPersistence; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.service.BukkitService; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -31,6 +32,9 @@ class LimboPlayerViewer implements DebugSection { @Inject private BukkitService bukkitService; + @Inject + private PermissionsManager permissionsManager; + @Override public String getName() { return "limbo"; @@ -64,8 +68,7 @@ class LimboPlayerViewer implements DebugSection { .sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight) .sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed) .sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation())) - .sendEntry("Group", LimboPlayer::getGroup, p -> ""); - sender.sendMessage("Note: group is not shown for Player. Use /authme debug groups"); + .sendEntry("Group", LimboPlayer::getGroup, permissionsManager::getPrimaryGroup); } /** 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 index e3876ff0a..a7bf19eb1 100644 --- 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 @@ -35,6 +35,7 @@ class PermissionGroups implements DebugSection { } else { sender.sendMessage("Player " + name + " has permission groups: " + String.join(", ", permissionsManager.getGroups(player))); + sender.sendMessage("Primary group is: " + permissionsManager.getGroups(player)); } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java new file mode 100644 index 000000000..0262055e3 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java @@ -0,0 +1,78 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.SpawnLoader; +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import java.util.List; + +import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation; + +/** + * Shows the spawn location that AuthMe is configured to use. + */ +class SpawnLocationViewer implements DebugSection { + + @Inject + private SpawnLoader spawnLoader; + + @Inject + private Settings settings; + + @Inject + private BukkitService bukkitService; + + + @Override + public String getName() { + return "spawn"; + } + + @Override + public String getDescription() { + return "Shows the spawn location that AuthMe will use"; + } + + @Override + public void execute(CommandSender sender, List arguments) { + if (arguments.isEmpty()) { + showGeneralInfo(sender); + } else if ("?".equals(arguments.get(0))) { + showHelp(sender); + } else { + showPlayerSpawn(sender, arguments.get(0)); + } + } + + private void showGeneralInfo(CommandSender sender) { + sender.sendMessage("Spawn priority: " + + String.join(", ", settings.getProperty(RestrictionSettings.SPAWN_PRIORITY))); + sender.sendMessage("AuthMe spawn location: " + formatLocation(spawnLoader.getSpawn())); + sender.sendMessage("AuthMe first spawn location: " + formatLocation(spawnLoader.getFirstSpawn())); + sender.sendMessage("AuthMe (first)spawn are only used depending on the configured priority!"); + sender.sendMessage("Use '/authme debug spawn ?' for further help"); + } + + private void showHelp(CommandSender sender) { + sender.sendMessage("Use /authme spawn and /authme firstspawn to teleport to the spawns."); + sender.sendMessage("/authme set(first)spawn sets the (first) spawn to your current location."); + sender.sendMessage("Use /authme debug spawn to view where a player would be teleported to."); + sender.sendMessage("Read more at https://github.com/AuthMe/AuthMeReloaded/wiki/Spawn-Handling"); + } + + private void showPlayerSpawn(CommandSender sender, String playerName) { + Player player = bukkitService.getPlayerExact(playerName); + if (player == null) { + sender.sendMessage("Player '" + playerName + "' is not online!"); + } else { + Location spawn = spawnLoader.getSpawnLocation(player); + sender.sendMessage("Player '" + playerName + "' has spawn location: " + formatLocation(spawn)); + sender.sendMessage("Note: this check excludes the AuthMe firstspawn."); + } + } +} 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 95ce4fb2a..04ba276a6 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 @@ -4,6 +4,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.mail.SendMailSsl; +import fr.xephi.authme.util.StringUtils; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.HtmlEmail; import org.bukkit.ChatColor; @@ -76,7 +77,7 @@ class TestEmailSender implements DebugSection { return email; } else { String email = arguments.get(0); - if (email.contains("@")) { + if (StringUtils.isInsideString('@', email)) { return email; } sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com"); diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java index 06a8761ab..c7644e083 100644 --- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java +++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java @@ -29,7 +29,7 @@ import java.util.regex.Pattern; /** * Service for performing various verifications when a player joins. */ -class OnJoinVerifier implements Reloadable { +public class OnJoinVerifier implements Reloadable { @Inject private Settings settings; From 75f84945fc9912c3df9e4e1033f4a409d9910910 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 26 Mar 2017 13:20:40 +0200 Subject: [PATCH 119/125] Misc code householding - Checkstyle config: allow todo comments with issue number - Create consistency tests across all classes, ensuring: unique class names, users of expiring collectors implement HasCleanup, non-private fields are only constants - Fix tag replacement in PlayerListener for {DISPLAYNAME} --- .checkstyle.xml | 4 +- .../authme/debug/HasPermissionChecker.java | 3 +- .../xephi/authme/listener/PlayerListener.java | 2 +- .../service/PasswordRecoveryService.java | 9 +- .../authme/util/expiring/ExpiringMap.java | 9 +- .../authme/util/expiring/TimedCounter.java | 6 +- .../xephi/authme/ClassesConsistencyTest.java | 182 ++++++++++++++++++ .../authme/util/expiring/ExpiringMapTest.java | 2 +- .../util/expiring/TimedCounterTest.java | 4 +- 9 files changed, 210 insertions(+), 11 deletions(-) create mode 100644 src/test/java/fr/xephi/authme/ClassesConsistencyTest.java diff --git a/.checkstyle.xml b/.checkstyle.xml index 017242e36..9a91018c8 100644 --- a/.checkstyle.xml +++ b/.checkstyle.xml @@ -28,7 +28,8 @@ - + + @@ -156,6 +157,7 @@ +