From ed9c5ef8a72ab3f4d0ad09acb5f8428eb9a51571 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sun, 5 Mar 2017 10:19:11 +0100 Subject: [PATCH 01/41] 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 02/41] 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 03/41] 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 04/41] #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 05/41] #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 06/41] 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 07/41] 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 4bb10c5d6d6b142d5001ee89c99aaa9925cbcb62 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 7 Mar 2017 20:35:48 +0100 Subject: [PATCH 08/41] #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 09/41] #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 10/41] 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 11/41] 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 12/41] 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 13/41] #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 14/41] #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 15/41] 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 16/41] #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 17/41] #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 18/41] 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 19/41] #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 20/41] 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 21/41] #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 22/41] #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 23/41] 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 24/41] 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 25/41] #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 26/41] #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 27/41] 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 28/41] #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 29/41] #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 457c07b53f57e6e65c666bdb02ba08754da1c859 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 15 Mar 2017 08:24:40 +0100 Subject: [PATCH 30/41] 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 31/41] 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 32/41] 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 33/41] 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 34/41] #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 35/41] #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 36/41] #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 37/41] 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 38/41] 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 39/41] #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 40/41] #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 d19748fe5bc625c42f2cb772319aa222dfe2f10b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 21 Mar 2017 22:00:21 +0100 Subject: [PATCH 41/41] #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() + "'"))); + } +}