diff --git a/.checkstyle.xml b/.checkstyle.xml
index 9a91018c..d12ab0d4 100644
--- a/.checkstyle.xml
+++ b/.checkstyle.xml
@@ -150,7 +150,7 @@
-
+
diff --git a/docs/config.md b/docs/config.md
index b0cbafd6..47744c22 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,
@@ -58,6 +58,9 @@ DataSource:
mySQLlastlocPitch: 'pitch'
# Overrides the size of the DB Connection Pool, -1 = Auto
poolSize: -1
+ # The maximum lifetime of a connection in the pool, default = 1800 seconds
+ # You should set this at least 30 seconds less than mysql server wait_timeout
+ maxLifetime: 1800
ExternalBoardOptions:
# Column for storing players passwords salts
mySQLColumnSalt: ''
@@ -194,8 +197,10 @@ settings:
maxJoinPerIp: 0
# AuthMe will NEVER teleport players if set to true!
noTeleport: false
- # Regex syntax for allowed chars in passwords
- allowedPasswordCharacters: '[\x21-\x7E]*'
+ # Regex syntax for allowed chars in passwords. The default [!-~] allows all visible ASCII
+ # characters, which is what we recommend. See also http://asciitable.com
+ # You can test your regex with https://regex101.com
+ allowedPasswordCharacters: '[!-~]*'
# Threshold of the other accounts command, a value less than 2 means disabled.
otherAccountsCmdThreshold: 0
# Command to run when a user has more accounts than the configured threshold.
@@ -476,8 +481,9 @@ limbo:
# 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.
distributionSize: 'SIXTEEN'
- # Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.
- # RESTORE sets back the old property from the player.
+ # Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE, NOTHING.
+ # RESTORE sets back the old property from the player. NOTHING will prevent AuthMe
+ # from modifying the 'allow flight' property on the player.
restoreAllowFlight: 'RESTORE'
# Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# RESTORE: restore the speed the player had;
@@ -487,7 +493,7 @@ limbo:
restoreFlySpeed: 'RESTORE_NO_ZERO'
# Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# See above for a description of the values.
- restoreWalkSpeed: 'MAX_RESTORE'
+ restoreWalkSpeed: 'RESTORE_NO_ZERO'
BackupSystem:
# General configuration for backups: if false, no backups are possible
ActivateBackup: false
@@ -528,4 +534,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 Thu Jul 06 18:45:51 CEST 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Jul 15 19:32:28 CEST 2017
diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java
index 083d8a53..acbb4757 100644
--- a/src/main/java/fr/xephi/authme/command/CommandHandler.java
+++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java
@@ -69,6 +69,12 @@ public class CommandHandler {
return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus());
}
+ /**
+ * Processes the given {@link FoundCommandResult} for the provided command sender.
+ *
+ * @param sender the command sender who executed the command
+ * @param result the command mapping result
+ */
private void handleCommandResult(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
index 2e18cb1a..948582fe 100644
--- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java
+++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
@@ -71,279 +71,13 @@ public class CommandInitializer {
/**
* Builds the command description objects for all available AuthMe commands.
*/
- @SuppressWarnings({"checkstyle:LocalVariableName", "checkstyle:AbbreviationAsWordInName"})
private void buildCommands() {
- // Register the base AuthMe Reloaded command
- final CommandDescription AUTHME_BASE = CommandDescription.builder()
- .labels("authme")
- .description("AuthMe op commands")
- .detailedDescription("The main AuthMeReloaded command. The root for all admin commands.")
- .executableCommand(AuthMeCommand.class)
- .register();
-
- // Register the register command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("register", "reg", "r")
- .description("Register a player")
- .detailedDescription("Register the specified player with the specified password.")
- .withArgument("player", "Player name", false)
- .withArgument("password", "Password", false)
- .permission(AdminPermission.REGISTER)
- .executableCommand(RegisterAdminCommand.class)
- .register();
-
- // Register the unregister command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("unregister", "unreg", "unr")
- .description("Unregister a player")
- .detailedDescription("Unregister the specified player.")
- .withArgument("player", "Player name", false)
- .permission(AdminPermission.UNREGISTER)
- .executableCommand(UnregisterAdminCommand.class)
- .register();
-
- // Register the forcelogin command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("forcelogin", "login")
- .description("Enforce login player")
- .detailedDescription("Enforce the specified player to login.")
- .withArgument("player", "Online player name", true)
- .permission(AdminPermission.FORCE_LOGIN)
- .executableCommand(ForceLoginCommand.class)
- .register();
-
- // Register the changepassword command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("password", "changepassword", "changepass", "cp")
- .description("Change a player's password")
- .detailedDescription("Change the password of a player.")
- .withArgument("player", "Player name", false)
- .withArgument("pwd", "New password", false)
- .permission(AdminPermission.CHANGE_PASSWORD)
- .executableCommand(ChangePasswordAdminCommand.class)
- .register();
-
- // Register the last login command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("lastlogin", "ll")
- .description("Player's last login")
- .detailedDescription("View the date of the specified players last login.")
- .withArgument("player", "Player name", true)
- .permission(AdminPermission.LAST_LOGIN)
- .executableCommand(LastLoginCommand.class)
- .register();
-
- // Register the accounts command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("accounts", "account")
- .description("Display player accounts")
- .detailedDescription("Display all accounts of a player by his player name or IP.")
- .withArgument("player", "Player name or IP", true)
- .permission(AdminPermission.ACCOUNTS)
- .executableCommand(AccountsCommand.class)
- .register();
-
- // Register the getemail command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("email", "mail", "getemail", "getmail")
- .description("Display player's email")
- .detailedDescription("Display the email address of the specified player if set.")
- .withArgument("player", "Player name", true)
- .permission(AdminPermission.GET_EMAIL)
- .executableCommand(GetEmailCommand.class)
- .register();
-
- // Register the setemail command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("setemail", "setmail", "chgemail", "chgmail")
- .description("Change player's email")
- .detailedDescription("Change the email address of the specified player.")
- .withArgument("player", "Player name", false)
- .withArgument("email", "Player email", false)
- .permission(AdminPermission.CHANGE_EMAIL)
- .executableCommand(SetEmailCommand.class)
- .register();
-
- // Register the getip command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("getip", "ip")
- .description("Get player's IP")
- .detailedDescription("Get the IP address of the specified online player.")
- .withArgument("player", "Player name", false)
- .permission(AdminPermission.GET_IP)
- .executableCommand(GetIpCommand.class)
- .register();
-
- // Register the spawn command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("spawn", "home")
- .description("Teleport to spawn")
- .detailedDescription("Teleport to the spawn.")
- .permission(AdminPermission.SPAWN)
- .executableCommand(SpawnCommand.class)
- .register();
-
- // Register the setspawn command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("setspawn", "chgspawn")
- .description("Change the spawn")
- .detailedDescription("Change the player's spawn to your current position.")
- .permission(AdminPermission.SET_SPAWN)
- .executableCommand(SetSpawnCommand.class)
- .register();
-
- // Register the firstspawn command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("firstspawn", "firsthome")
- .description("Teleport to first spawn")
- .detailedDescription("Teleport to the first spawn.")
- .permission(AdminPermission.FIRST_SPAWN)
- .executableCommand(FirstSpawnCommand.class)
- .register();
-
- // Register the setfirstspawn command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("setfirstspawn", "chgfirstspawn")
- .description("Change the first spawn")
- .detailedDescription("Change the first player's spawn to your current position.")
- .permission(AdminPermission.SET_FIRST_SPAWN)
- .executableCommand(SetFirstSpawnCommand.class)
- .register();
-
- // Register the purge command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("purge", "delete")
- .description("Purge old data")
- .detailedDescription("Purge old AuthMeReloaded data longer than the specified number of days ago.")
- .withArgument("days", "Number of days", false)
- .withArgument("all", "Add 'all' at the end to also purge players with lastlogin = 0", true)
- .permission(AdminPermission.PURGE)
- .executableCommand(PurgeCommand.class)
- .register();
-
- // Purge player command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("purgeplayer")
- .description("Purges the data of one player")
- .detailedDescription("Purges data of the given player.")
- .withArgument("player", "The player to purge", false)
- .withArgument("options", "'force' to run without checking if player is registered", true)
- .permission(AdminPermission.PURGE_PLAYER)
- .executableCommand(PurgePlayerCommand.class)
- .register();
-
- // Backup command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("backup")
- .description("Perform a backup")
- .detailedDescription("Creates a backup of the registered users.")
- .permission(AdminPermission.BACKUP)
- .executableCommand(BackupCommand.class)
- .register();
-
- // Register the purgelastposition command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("resetpos", "purgelastposition", "purgelastpos", "resetposition",
- "resetlastposition", "resetlastpos")
- .description("Purge player's last position")
- .detailedDescription("Purge the last know position of the specified player or all of them.")
- .withArgument("player/*", "Player name or * for all players", false)
- .permission(AdminPermission.PURGE_LAST_POSITION)
- .executableCommand(PurgeLastPositionCommand.class)
- .register();
-
- // Register the purgebannedplayers command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("purgebannedplayers", "purgebannedplayer", "deletebannedplayers", "deletebannedplayer")
- .description("Purge banned players data")
- .detailedDescription("Purge all AuthMeReloaded data for banned players.")
- .permission(AdminPermission.PURGE_BANNED_PLAYERS)
- .executableCommand(PurgeBannedPlayersCommand.class)
- .register();
-
- // Register the switchantibot command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("switchantibot", "toggleantibot", "antibot")
- .description("Switch AntiBot mode")
- .detailedDescription("Switch or toggle the AntiBot mode to the specified state.")
- .withArgument("mode", "ON / OFF", true)
- .permission(AdminPermission.SWITCH_ANTIBOT)
- .executableCommand(SwitchAntiBotCommand.class)
- .register();
-
- // Register the reload command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("reload", "rld")
- .description("Reload plugin")
- .detailedDescription("Reload the AuthMeReloaded plugin.")
- .permission(AdminPermission.RELOAD)
- .executableCommand(ReloadCommand.class)
- .register();
-
- // Register the version command
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("version", "ver", "v", "about", "info")
- .description("Version info")
- .detailedDescription("Show detailed information about the installed AuthMeReloaded version, the "
- + "developers, contributors, and license.")
- .executableCommand(VersionCommand.class)
- .register();
-
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("converter", "convert", "conv")
- .description("Converter command")
- .detailedDescription("Converter command for AuthMeReloaded.")
- .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
- + "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", true)
- .permission(AdminPermission.CONVERTER)
- .executableCommand(ConverterCommand.class)
- .register();
-
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("messages", "msg")
- .description("Add missing messages")
- .detailedDescription("Adds missing messages to the current messages file.")
- .permission(AdminPermission.UPDATE_MESSAGES)
- .executableCommand(MessagesCommand.class)
- .register();
-
- CommandDescription.builder()
- .parent(AUTHME_BASE)
- .labels("debug", "dbg")
- .description("Debug features")
- .detailedDescription("Allows various operations for debugging.")
- .withArgument("child", "The child to execute", true)
- .withArgument("arg", "argument (depends on debug section)", true)
- .withArgument("arg", "argument (depends on debug section)", true)
- .permission(DebugSectionPermissions.DEBUG_COMMAND)
- .executableCommand(DebugCommand.class)
- .register();
+ // Register /authme and /email commands
+ CommandDescription authMeBase = buildAuthMeBaseCommand();
+ CommandDescription emailBase = buildEmailBaseCommand();
// Register the base login command
- final CommandDescription LOGIN_BASE = CommandDescription.builder()
+ CommandDescription loginBase = CommandDescription.builder()
.parent(null)
.labels("login", "l", "log")
.description("Login command")
@@ -354,7 +88,7 @@ public class CommandInitializer {
.register();
// Register the base logout command
- CommandDescription LOGOUT_BASE = CommandDescription.builder()
+ CommandDescription logoutBase = CommandDescription.builder()
.parent(null)
.labels("logout")
.description("Logout command")
@@ -364,7 +98,7 @@ public class CommandInitializer {
.register();
// Register the base register command
- final CommandDescription REGISTER_BASE = CommandDescription.builder()
+ CommandDescription registerBase = CommandDescription.builder()
.parent(null)
.labels("register", "reg")
.description("Register an account")
@@ -376,7 +110,7 @@ public class CommandInitializer {
.register();
// Register the base unregister command
- CommandDescription UNREGISTER_BASE = CommandDescription.builder()
+ CommandDescription unregisterBase = CommandDescription.builder()
.parent(null)
.labels("unregister", "unreg")
.description("Unregister an account")
@@ -387,7 +121,7 @@ public class CommandInitializer {
.register();
// Register the base changepassword command
- final CommandDescription CHANGE_PASSWORD_BASE = CommandDescription.builder()
+ CommandDescription changePasswordBase = CommandDescription.builder()
.parent(null)
.labels("changepassword", "changepass", "cp")
.description("Change password of an account")
@@ -398,8 +132,317 @@ public class CommandInitializer {
.executableCommand(ChangePasswordCommand.class)
.register();
+ // Register the base captcha command
+ CommandDescription captchaBase = CommandDescription.builder()
+ .parent(null)
+ .labels("captcha")
+ .description("Captcha Command")
+ .detailedDescription("Captcha command for AuthMeReloaded.")
+ .withArgument("captcha", "The Captcha", false)
+ .permission(PlayerPermission.CAPTCHA)
+ .executableCommand(CaptchaCommand.class)
+ .register();
+
+ List baseCommands = ImmutableList.of(
+ authMeBase,
+ emailBase,
+ loginBase,
+ logoutBase,
+ registerBase,
+ unregisterBase,
+ changePasswordBase,
+ captchaBase);
+
+ setHelpOnAllBases(baseCommands);
+ commands = baseCommands;
+ }
+
+ /**
+ * Creates a command description object for {@code /authme} including its children.
+ *
+ * @return the authme base command description
+ */
+ private CommandDescription buildAuthMeBaseCommand() {
+ // Register the base AuthMe Reloaded command
+ CommandDescription authmeBase = CommandDescription.builder()
+ .labels("authme")
+ .description("AuthMe op commands")
+ .detailedDescription("The main AuthMeReloaded command. The root for all admin commands.")
+ .executableCommand(AuthMeCommand.class)
+ .register();
+
+ // Register the register command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("register", "reg", "r")
+ .description("Register a player")
+ .detailedDescription("Register the specified player with the specified password.")
+ .withArgument("player", "Player name", false)
+ .withArgument("password", "Password", false)
+ .permission(AdminPermission.REGISTER)
+ .executableCommand(RegisterAdminCommand.class)
+ .register();
+
+ // Register the unregister command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("unregister", "unreg", "unr")
+ .description("Unregister a player")
+ .detailedDescription("Unregister the specified player.")
+ .withArgument("player", "Player name", false)
+ .permission(AdminPermission.UNREGISTER)
+ .executableCommand(UnregisterAdminCommand.class)
+ .register();
+
+ // Register the forcelogin command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("forcelogin", "login")
+ .description("Enforce login player")
+ .detailedDescription("Enforce the specified player to login.")
+ .withArgument("player", "Online player name", true)
+ .permission(AdminPermission.FORCE_LOGIN)
+ .executableCommand(ForceLoginCommand.class)
+ .register();
+
+ // Register the changepassword command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("password", "changepassword", "changepass", "cp")
+ .description("Change a player's password")
+ .detailedDescription("Change the password of a player.")
+ .withArgument("player", "Player name", false)
+ .withArgument("pwd", "New password", false)
+ .permission(AdminPermission.CHANGE_PASSWORD)
+ .executableCommand(ChangePasswordAdminCommand.class)
+ .register();
+
+ // Register the last login command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("lastlogin", "ll")
+ .description("Player's last login")
+ .detailedDescription("View the date of the specified players last login.")
+ .withArgument("player", "Player name", true)
+ .permission(AdminPermission.LAST_LOGIN)
+ .executableCommand(LastLoginCommand.class)
+ .register();
+
+ // Register the accounts command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("accounts", "account")
+ .description("Display player accounts")
+ .detailedDescription("Display all accounts of a player by his player name or IP.")
+ .withArgument("player", "Player name or IP", true)
+ .permission(AdminPermission.ACCOUNTS)
+ .executableCommand(AccountsCommand.class)
+ .register();
+
+ // Register the getemail command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("email", "mail", "getemail", "getmail")
+ .description("Display player's email")
+ .detailedDescription("Display the email address of the specified player if set.")
+ .withArgument("player", "Player name", true)
+ .permission(AdminPermission.GET_EMAIL)
+ .executableCommand(GetEmailCommand.class)
+ .register();
+
+ // Register the setemail command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("setemail", "setmail", "chgemail", "chgmail")
+ .description("Change player's email")
+ .detailedDescription("Change the email address of the specified player.")
+ .withArgument("player", "Player name", false)
+ .withArgument("email", "Player email", false)
+ .permission(AdminPermission.CHANGE_EMAIL)
+ .executableCommand(SetEmailCommand.class)
+ .register();
+
+ // Register the getip command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("getip", "ip")
+ .description("Get player's IP")
+ .detailedDescription("Get the IP address of the specified online player.")
+ .withArgument("player", "Player name", false)
+ .permission(AdminPermission.GET_IP)
+ .executableCommand(GetIpCommand.class)
+ .register();
+
+ // Register the spawn command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("spawn", "home")
+ .description("Teleport to spawn")
+ .detailedDescription("Teleport to the spawn.")
+ .permission(AdminPermission.SPAWN)
+ .executableCommand(SpawnCommand.class)
+ .register();
+
+ // Register the setspawn command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("setspawn", "chgspawn")
+ .description("Change the spawn")
+ .detailedDescription("Change the player's spawn to your current position.")
+ .permission(AdminPermission.SET_SPAWN)
+ .executableCommand(SetSpawnCommand.class)
+ .register();
+
+ // Register the firstspawn command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("firstspawn", "firsthome")
+ .description("Teleport to first spawn")
+ .detailedDescription("Teleport to the first spawn.")
+ .permission(AdminPermission.FIRST_SPAWN)
+ .executableCommand(FirstSpawnCommand.class)
+ .register();
+
+ // Register the setfirstspawn command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("setfirstspawn", "chgfirstspawn")
+ .description("Change the first spawn")
+ .detailedDescription("Change the first player's spawn to your current position.")
+ .permission(AdminPermission.SET_FIRST_SPAWN)
+ .executableCommand(SetFirstSpawnCommand.class)
+ .register();
+
+ // Register the purge command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("purge", "delete")
+ .description("Purge old data")
+ .detailedDescription("Purge old AuthMeReloaded data longer than the specified number of days ago.")
+ .withArgument("days", "Number of days", false)
+ .withArgument("all", "Add 'all' at the end to also purge players with lastlogin = 0", true)
+ .permission(AdminPermission.PURGE)
+ .executableCommand(PurgeCommand.class)
+ .register();
+
+ // Purge player command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("purgeplayer")
+ .description("Purges the data of one player")
+ .detailedDescription("Purges data of the given player.")
+ .withArgument("player", "The player to purge", false)
+ .withArgument("options", "'force' to run without checking if player is registered", true)
+ .permission(AdminPermission.PURGE_PLAYER)
+ .executableCommand(PurgePlayerCommand.class)
+ .register();
+
+ // Backup command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("backup")
+ .description("Perform a backup")
+ .detailedDescription("Creates a backup of the registered users.")
+ .permission(AdminPermission.BACKUP)
+ .executableCommand(BackupCommand.class)
+ .register();
+
+ // Register the purgelastposition command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("resetpos", "purgelastposition", "purgelastpos", "resetposition",
+ "resetlastposition", "resetlastpos")
+ .description("Purge player's last position")
+ .detailedDescription("Purge the last know position of the specified player or all of them.")
+ .withArgument("player/*", "Player name or * for all players", false)
+ .permission(AdminPermission.PURGE_LAST_POSITION)
+ .executableCommand(PurgeLastPositionCommand.class)
+ .register();
+
+ // Register the purgebannedplayers command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("purgebannedplayers", "purgebannedplayer", "deletebannedplayers", "deletebannedplayer")
+ .description("Purge banned players data")
+ .detailedDescription("Purge all AuthMeReloaded data for banned players.")
+ .permission(AdminPermission.PURGE_BANNED_PLAYERS)
+ .executableCommand(PurgeBannedPlayersCommand.class)
+ .register();
+
+ // Register the switchantibot command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("switchantibot", "toggleantibot", "antibot")
+ .description("Switch AntiBot mode")
+ .detailedDescription("Switch or toggle the AntiBot mode to the specified state.")
+ .withArgument("mode", "ON / OFF", true)
+ .permission(AdminPermission.SWITCH_ANTIBOT)
+ .executableCommand(SwitchAntiBotCommand.class)
+ .register();
+
+ // Register the reload command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("reload", "rld")
+ .description("Reload plugin")
+ .detailedDescription("Reload the AuthMeReloaded plugin.")
+ .permission(AdminPermission.RELOAD)
+ .executableCommand(ReloadCommand.class)
+ .register();
+
+ // Register the version command
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("version", "ver", "v", "about", "info")
+ .description("Version info")
+ .detailedDescription("Show detailed information about the installed AuthMeReloaded version, the "
+ + "developers, contributors, and license.")
+ .executableCommand(VersionCommand.class)
+ .register();
+
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("converter", "convert", "conv")
+ .description("Converter command")
+ .detailedDescription("Converter command for AuthMeReloaded.")
+ .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
+ + "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", true)
+ .permission(AdminPermission.CONVERTER)
+ .executableCommand(ConverterCommand.class)
+ .register();
+
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("messages", "msg")
+ .description("Add missing messages")
+ .detailedDescription("Adds missing messages to the current messages file.")
+ .permission(AdminPermission.UPDATE_MESSAGES)
+ .executableCommand(MessagesCommand.class)
+ .register();
+
+ CommandDescription.builder()
+ .parent(authmeBase)
+ .labels("debug", "dbg")
+ .description("Debug features")
+ .detailedDescription("Allows various operations for debugging.")
+ .withArgument("child", "The child to execute", true)
+ .withArgument("arg", "argument (depends on debug section)", true)
+ .withArgument("arg", "argument (depends on debug section)", true)
+ .permission(DebugSectionPermissions.DEBUG_COMMAND)
+ .executableCommand(DebugCommand.class)
+ .register();
+
+ return authmeBase;
+ }
+
+ /**
+ * Creates a command description for {@code /email} including its children.
+ *
+ * @return the email base command description
+ */
+ private CommandDescription buildEmailBaseCommand() {
// Register the base Email command
- CommandDescription EMAIL_BASE = CommandDescription.builder()
+ CommandDescription emailBase = CommandDescription.builder()
.parent(null)
.labels("email")
.description("Add email or recover password")
@@ -409,7 +452,7 @@ public class CommandInitializer {
// Register the show command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("show", "myemail")
.description("Show Email")
.detailedDescription("Show your current email address.")
@@ -418,7 +461,7 @@ public class CommandInitializer {
// Register the add command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("add", "addemail", "addmail")
.description("Add Email")
.detailedDescription("Add a new email address to your account.")
@@ -430,7 +473,7 @@ public class CommandInitializer {
// Register the change command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("change", "changeemail", "changemail")
.description("Change Email")
.detailedDescription("Change an email address of your account.")
@@ -442,7 +485,7 @@ public class CommandInitializer {
// Register the recover command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("recover", "recovery", "recoveremail", "recovermail")
.description("Recover password using email")
.detailedDescription("Recover your account using an Email address by sending a mail containing "
@@ -454,7 +497,7 @@ public class CommandInitializer {
// Register the process recovery code command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("code")
.description("Submit code to recover password")
.detailedDescription("Recover your account by submitting a code delivered to your email.")
@@ -465,7 +508,7 @@ public class CommandInitializer {
// Register the change password after recovery command
CommandDescription.builder()
- .parent(EMAIL_BASE)
+ .parent(emailBase)
.labels("setpassword")
.description("Set new password after recovery")
.detailedDescription("Set a new password after successfully recovering your account.")
@@ -474,29 +517,7 @@ public class CommandInitializer {
.executableCommand(SetPasswordCommand.class)
.register();
- // Register the base captcha command
- CommandDescription CAPTCHA_BASE = CommandDescription.builder()
- .parent(null)
- .labels("captcha")
- .description("Captcha Command")
- .detailedDescription("Captcha command for AuthMeReloaded.")
- .withArgument("captcha", "The Captcha", false)
- .permission(PlayerPermission.CAPTCHA)
- .executableCommand(CaptchaCommand.class)
- .register();
-
- List baseCommands = ImmutableList.of(
- AUTHME_BASE,
- LOGIN_BASE,
- LOGOUT_BASE,
- REGISTER_BASE,
- UNREGISTER_BASE,
- CHANGE_PASSWORD_BASE,
- EMAIL_BASE,
- CAPTCHA_BASE);
-
- setHelpOnAllBases(baseCommands);
- commands = baseCommands;
+ return emailBase;
}
/**
diff --git a/src/main/java/fr/xephi/authme/command/CommandUtils.java b/src/main/java/fr/xephi/authme/command/CommandUtils.java
index 90d0138a..391a1463 100644
--- a/src/main/java/fr/xephi/authme/command/CommandUtils.java
+++ b/src/main/java/fr/xephi/authme/command/CommandUtils.java
@@ -5,7 +5,6 @@ import org.bukkit.ChatColor;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Utility functions for {@link CommandDescription} objects.
@@ -76,19 +75,14 @@ public final class CommandUtils {
}
/**
- * Returns a textual representation of the command, including its arguments.
- * For example: {@code /authme purge [includeZero]}.
+ * Constructs a command path with color formatting, based on the supplied labels. This includes
+ * the command's arguments, as defined in the provided command description. The list of labels
+ * must contain all labels to be used.
*
- * @param command the command to create a usage string for
- * @return the command's path and arguments
+ * @param command the command to read arguments from
+ * @param correctLabels the labels to use (must be complete)
+ * @return formatted command syntax incl. arguments
*/
- public static String buildSyntax(CommandDescription command) {
- String arguments = command.getArguments().stream()
- .map(arg -> formatArgument(arg))
- .collect(Collectors.joining(" "));
- return (constructCommandPath(command) + " " + arguments).trim();
- }
-
public static String buildSyntax(CommandDescription command, List correctLabels) {
String commandSyntax = ChatColor.WHITE + "/" + correctLabels.get(0) + ChatColor.YELLOW;
for (int i = 1; i < correctLabels.size(); ++i) {
@@ -106,7 +100,7 @@ public final class CommandUtils {
* @param argument the argument to format
* @return the formatted argument
*/
- private static String formatArgument(CommandArgumentDescription argument) {
+ public static String formatArgument(CommandArgumentDescription argument) {
if (argument.isOptional()) {
return "[" + argument.getName() + "]";
}
diff --git a/src/main/java/fr/xephi/authme/data/TempbanManager.java b/src/main/java/fr/xephi/authme/data/TempbanManager.java
index 1d55fa8f..6256fa1e 100644
--- a/src/main/java/fr/xephi/authme/data/TempbanManager.java
+++ b/src/main/java/fr/xephi/authme/data/TempbanManager.java
@@ -119,7 +119,7 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
this.isEnabled = settings.getProperty(SecuritySettings.TEMPBAN_ON_MAX_LOGINS);
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TEMPBAN);
this.length = settings.getProperty(SecuritySettings.TEMPBAN_LENGTH);
- this.resetThreshold = settings.getProperty(TEMPBAN_MINUTES_BEFORE_RESET) * MILLIS_PER_MINUTE;
+ this.resetThreshold = settings.getProperty(TEMPBAN_MINUTES_BEFORE_RESET);
}
@Override
diff --git a/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java
index f085afbb..52388521 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java
@@ -2,8 +2,6 @@ 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.
@@ -11,24 +9,41 @@ import java.util.function.Function;
public enum AllowFlightRestoreType {
/** Set value from LimboPlayer to Player. */
- RESTORE(LimboPlayer::isCanFly),
+ RESTORE {
+ @Override
+ public void restoreAllowFlight(Player player, LimboPlayer limbo) {
+ player.setAllowFlight(limbo.isCanFly());
+ }
+ },
/** Always set flight enabled to true. */
- ENABLE(l -> true),
+ ENABLE {
+ @Override
+ public void restoreAllowFlight(Player player, LimboPlayer limbo) {
+ player.setAllowFlight(true);
+ }
+ },
/** Always set flight enabled to false. */
- DISABLE(l -> false);
+ DISABLE {
+ @Override
+ public void restoreAllowFlight(Player player, LimboPlayer limbo) {
+ player.setAllowFlight(false);
+ }
+ },
- private final Function valueGetter;
+ /** Always set flight enabled to false. */
+ NOTHING {
+ @Override
+ public void restoreAllowFlight(Player player, LimboPlayer limbo) {
+ // noop
+ }
- /**
- * Constructor.
- *
- * @param valueGetter function with which the value to set on the player can be retrieved
- */
- AllowFlightRestoreType(Function valueGetter) {
- this.valueGetter = valueGetter;
- }
+ @Override
+ public void processPlayer(Player player) {
+ // noop
+ }
+ };
/**
* Restores the "allow flight" property from the LimboPlayer to the Player.
@@ -37,7 +52,15 @@ public enum AllowFlightRestoreType {
* @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));
+ public abstract void restoreAllowFlight(Player player, LimboPlayer limbo);
+
+ /**
+ * Processes the player when a LimboPlayer instance is created based on him. Typically this
+ * method revokes the {@code allowFlight} property to be restored again later.
+ *
+ * @param player the player to process
+ */
+ public void processPlayer(Player player) {
+ player.setAllowFlight(false);
}
}
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 4e8248e3..dc386389 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
@@ -4,6 +4,7 @@ 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.LimboSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -59,7 +60,8 @@ class LimboServiceHelper {
*/
void revokeLimboStates(Player player) {
player.setOp(false);
- player.setAllowFlight(false);
+ settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)
+ .processPlayer(player);
if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) {
player.setFlySpeed(0.0f);
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandler.java
index b3c0cc90..16b91963 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandler.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandler.java
@@ -114,7 +114,7 @@ class DistributedFilesPersistenceHandler implements LimboPersistenceHandler {
}
try {
- return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE);
+ return gson.fromJson(Files.asCharSource(file, StandardCharsets.UTF_8).read(), LIMBO_MAP_TYPE);
} catch (Exception e) {
ConsoleLogger.logException("Failed reading '" + file + "':", e);
}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandler.java
index 880c463e..1ff61c83 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandler.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandler.java
@@ -46,7 +46,7 @@ class IndividualFilesPersistenceHandler implements LimboPersistenceHandler {
}
try {
- String str = Files.toString(file, StandardCharsets.UTF_8);
+ String str = Files.asCharSource(file, StandardCharsets.UTF_8).read();
return gson.fromJson(str, LimboPlayer.class);
} catch (IOException e) {
ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
index 4d85a31c..bb9aa3ef 100644
--- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java
+++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
@@ -6,7 +6,6 @@ import fr.xephi.authme.security.crypts.HashedPassword;
import java.io.BufferedReader;
import java.io.BufferedWriter;
-import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
@@ -80,7 +79,9 @@ public class FlatFile implements DataSource {
return false;
}
try (BufferedWriter bw = new BufferedWriter(new FileWriter(source, true))) {
- bw.write(auth.getNickname() + ":" + auth.getPassword().getHash() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n");
+ bw.write(auth.getNickname() + ":" + auth.getPassword().getHash() + ":" + auth.getIp()
+ + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY()
+ + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n");
} catch (IOException ex) {
ConsoleLogger.warning(ex.getMessage());
return false;
@@ -203,7 +204,7 @@ public class FlatFile implements DataSource {
return false;
}
ArrayList lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(new FileReader(source));) {
+ try (BufferedReader br = new BufferedReader(new FileReader(source))) {
String line;
while ((line = br.readLine()) != null) {
@@ -379,6 +380,13 @@ public class FlatFile implements DataSource {
throw new UnsupportedOperationException("Flat file no longer supported");
}
+ /**
+ * Creates a PlayerAuth object from the read data.
+ *
+ * @param args the data read from the line
+ * @return the player auth object with the data
+ */
+ @SuppressWarnings("checkstyle:NeedBraces")
private static PlayerAuth buildAuthFromArray(String[] args) {
// Format allows 2, 3, 4, 7, 8, 9 fields. Anything else is unknown
if (args.length >= 2 && args.length <= 9 && args.length != 5 && args.length != 6) {
diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index 9c981631..b86ad716 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -41,6 +41,7 @@ public class MySQL implements DataSource {
private String database;
private String tableName;
private int poolSize;
+ private int maxLifetime;
private List columnOthers;
private Columns col;
private HashAlgorithm hashAlgorithm;
@@ -116,6 +117,7 @@ public class MySQL implements DataSource {
if (poolSize == -1) {
poolSize = Utils.getCoreCount() * 3;
}
+ this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME);
this.useSsl = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
}
@@ -126,8 +128,10 @@ public class MySQL implements DataSource {
ds = new HikariDataSource();
ds.setPoolName("AuthMeMYSQLPool");
- // Pool size
+ // Pool Settings
ds.setMaximumPoolSize(poolSize);
+ ds.setMaxLifetime(maxLifetime * 1000);
+
// Database URL
ds.setJdbcUrl("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database);
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 d5dbeaca..043d5c17 100644
--- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
+++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
@@ -73,6 +73,11 @@ public class AsynchronousJoin implements AsynchronousProcess {
AsynchronousJoin() {
}
+ /**
+ * Processes the given player that has just joined.
+ *
+ * @param player the player to process
+ */
public void processJoin(final Player player) {
final String name = player.getName().toLowerCase();
final String ip = PlayerUtils.getPlayerIp(player);
@@ -91,15 +96,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
}
if (!validationService.fulfillsNameRestrictions(player)) {
- bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
- @Override
- public void run() {
- player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR));
- if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) {
- server.banIP(ip);
- }
- }
- });
+ handlePlayerWithUnmetNameRestriction(player, ip);
return;
}
@@ -124,7 +121,8 @@ public class AsynchronousJoin implements AsynchronousProcess {
if (canResumeSession(player)) {
service.send(player, MessageKey.SESSION_RECONNECTION);
// Run commands
- bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> commandManager.runCommandsOnSessionLogin(player));
+ bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(
+ () -> commandManager.runCommandsOnSessionLogin(player));
bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player));
return;
}
@@ -133,6 +131,26 @@ public class AsynchronousJoin implements AsynchronousProcess {
return;
}
+ processJoinSync(player, isAuthAvailable);
+ }
+
+ private void handlePlayerWithUnmetNameRestriction(Player player, String ip) {
+ bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
+ player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR));
+ if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) {
+ server.banIP(ip);
+ }
+ });
+ }
+
+ /**
+ * Performs various operations in sync mode for an unauthenticated player (such as blindness effect and
+ * limbo player creation).
+ *
+ * @param player the player to process
+ * @param isAuthAvailable true if the player is registered, false otherwise
+ */
+ private void processJoinSync(Player player, boolean isAuthAvailable) {
final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
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
index 027a5fa6..c84a70ab 100644
--- a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java
+++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java
@@ -37,7 +37,7 @@ class TwoFactorRegisterExecutor extends AbstractPasswordRegisterExecutorOriginal source
+ */
@Recommendation(Usage.DOES_NOT_WORK)
@HasSalt(SaltType.NONE)
public class TwoFactor extends UnsaltedMethod {
@@ -30,7 +35,15 @@ public class TwoFactor extends UnsaltedMethod {
private static final int TIME_PRECISION = 3;
private static final String CRYPTO_ALGO = "HmacSHA1";
- public static String getQRBarcodeURL(String user, String host, String secret) {
+ /**
+ * Creates a link to a QR barcode with the provided secret.
+ *
+ * @param user the player's name
+ * @param host the server host
+ * @param secret the TOTP secret
+ * @return URL leading to a QR code
+ */
+ public static String getQrBarcodeUrl(String user, String host, String secret) {
String format = "https://www.google.com/chart?chs=130x130&chld=M%%7C0&cht=qr&chl="
+ "otpauth://totp/"
+ "%s@%s%%3Fsecret%%3D%s";
@@ -72,18 +85,17 @@ public class TwoFactor extends UnsaltedMethod {
}
long currentTime = Calendar.getInstance().getTimeInMillis() / TimeUnit.SECONDS.toMillis(30);
- return check_code(secretKey, code, currentTime);
+ return checkCode(secretKey, code, currentTime);
}
- private boolean check_code(String secret, long code, long t)
- throws NoSuchAlgorithmException, InvalidKeyException {
+ private boolean checkCode(String secret, long code, long t) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] decodedKey = BaseEncoding.base32().decode(secret);
// Window is used to check codes generated in the near past.
// You can use this value to tune how far you're willing to go.
int window = TIME_PRECISION;
for (int i = -window; i <= window; ++i) {
- long hash = verify_code(decodedKey, t + i);
+ long hash = verifyCode(decodedKey, t + i);
if (hash == code) {
return true;
@@ -94,7 +106,7 @@ public class TwoFactor extends UnsaltedMethod {
return false;
}
- private int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
+ private int verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] data = new byte[8];
long value = t;
for (int i = 8; i-- > 0; value >>>= 8) {
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 bf554529..3ef4e430 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
@@ -42,6 +42,12 @@ public class XfBCrypt implements EncryptionMethod {
return false;
}
+ /**
+ * Extracts the password hash from the given BLOB.
+ *
+ * @param blob the blob to process
+ * @return the extracted hash
+ */
public static String getHashFromBlob(byte[] blob) {
String line = new String(blob);
Matcher m = HASH_PATTERN.matcher(line);
diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java
index 980f2add..f2a72251 100644
--- a/src/main/java/fr/xephi/authme/settings/Settings.java
+++ b/src/main/java/fr/xephi/authme/settings/Settings.java
@@ -76,7 +76,7 @@ public class Settings extends SettingsManager {
final File file = new File(pluginFolder, filename);
if (copyFileFromResource(file, filename)) {
try {
- return Files.toString(file, StandardCharsets.UTF_8);
+ return Files.asCharSource(file, StandardCharsets.UTF_8).read();
} catch (IOException e) {
ConsoleLogger.logException("Failed to read file '" + filename + "':", e);
}
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 91d8f5ec..f8344b4a 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);
@@ -114,6 +114,11 @@ public final class DatabaseSettings implements SettingsHolder {
public static final Property MYSQL_POOL_SIZE =
newProperty("DataSource.poolSize", -1);
+ @Comment({"The maximum lifetime of a connection in the pool, default = 1800 seconds",
+ "You should set this at least 30 seconds less than mysql server wait_timeout"})
+ public static final Property MYSQL_CONNECTION_MAX_LIFETIME =
+ newProperty("DataSource.maxLifetime", 1800);
+
private DatabaseSettings() {
}
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 719789ab..68a4b717 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/LimboSettings.java
@@ -45,8 +45,9 @@ public final class LimboSettings implements SettingsHolder {
newProperty(SegmentSize.class, "limbo.persistence.distributionSize", SegmentSize.SIXTEEN);
@Comment({
- "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.",
- "RESTORE sets back the old property from the player."
+ "Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE, NOTHING.",
+ "RESTORE sets back the old property from the player. NOTHING will prevent AuthMe",
+ "from modifying the 'allow flight' property on the player."
})
public static final Property RESTORE_ALLOW_FLIGHT =
newProperty(AllowFlightRestoreType.class, "limbo.restoreAllowFlight", AllowFlightRestoreType.RESTORE);
@@ -66,7 +67,7 @@ public final class LimboSettings implements SettingsHolder {
"See above for a description of the values."
})
public static final Property RESTORE_WALK_SPEED =
- newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreWalkSpeed", WalkFlySpeedRestoreType.MAX_RESTORE);
+ newProperty(WalkFlySpeedRestoreType.class, "limbo.restoreWalkSpeed", WalkFlySpeedRestoreType.RESTORE_NO_ZERO);
private LimboSettings() {
}
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 1a3de299..e2c1265d 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
@@ -156,9 +156,13 @@ public final class RestrictionSettings implements SettingsHolder {
public static final Property NO_TELEPORT =
newProperty("settings.restrictions.noTeleport", false);
- @Comment("Regex syntax for allowed chars in passwords")
+ @Comment({
+ "Regex syntax for allowed chars in passwords. The default [!-~] allows all visible ASCII",
+ "characters, which is what we recommend. See also http://asciitable.com",
+ "You can test your regex with https://regex101.com"
+ })
public static final Property ALLOWED_PASSWORD_REGEX =
- newProperty("settings.restrictions.allowedPasswordCharacters", "[\\x21-\\x7E]*");
+ newProperty("settings.restrictions.allowedPasswordCharacters", "[!-~]*");
@Comment("Force survival gamemode when player joins?")
public static final Property FORCE_SURVIVAL_MODE =
diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
index 13bd2923..16fd154d 100644
--- a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
+++ b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java
@@ -169,7 +169,6 @@ public class PurgeExecutor {
return;
}
- int i = 0;
File essentialsDataFolder = pluginHookService.getEssentialsDataFolder();
if (essentialsDataFolder == null) {
ConsoleLogger.info("Cannot purge Essentials: plugin is not loaded");
@@ -181,14 +180,15 @@ public class PurgeExecutor {
return;
}
+ int deletedFiles = 0;
for (OfflinePlayer offlinePlayer : cleared) {
File playerFile = new File(userDataFolder, PlayerUtils.getUuidOrName(offlinePlayer) + ".yml");
if (playerFile.exists() && playerFile.delete()) {
- i++;
+ deletedFiles++;
}
}
- ConsoleLogger.info("AutoPurge: Removed " + i + " EssentialsFiles");
+ ConsoleLogger.info("AutoPurge: Removed " + deletedFiles + " EssentialsFiles");
}
// TODO #676: What is this method for? Is it correct?
diff --git a/src/main/java/fr/xephi/authme/util/FileUtils.java b/src/main/java/fr/xephi/authme/util/FileUtils.java
index fa8c858a..759f5581 100644
--- a/src/main/java/fr/xephi/authme/util/FileUtils.java
+++ b/src/main/java/fr/xephi/authme/util/FileUtils.java
@@ -61,7 +61,7 @@ public final class FileUtils {
ConsoleLogger.warning("Could not create directory '" + dir + "'");
return false;
}
- return true;
+ return dir.isDirectory();
}
/**
diff --git a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
index 33ab451e..548e1e91 100644
--- a/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
+++ b/src/main/java/fr/xephi/authme/util/InternetProtocolUtils.java
@@ -5,11 +5,11 @@ import java.util.regex.Pattern;
/**
* Utility class about the InternetProtocol
*/
-public class InternetProtocolUtils {
+public final class InternetProtocolUtils {
- private final static Pattern LOCAL_ADDRESS_PATTERN =
- Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)" +
- "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)");
+ private static final Pattern LOCAL_ADDRESS_PATTERN =
+ Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)"
+ + "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)");
// Utility class
private InternetProtocolUtils() {
diff --git a/src/main/resources/messages/help_zhcn.yml b/src/main/resources/messages/help_zhcn.yml
new file mode 100644
index 00000000..3bf00864
--- /dev/null
+++ b/src/main/resources/messages/help_zhcn.yml
@@ -0,0 +1,213 @@
+# AuthmeReloaded帮助文件汉化
+# Translated By CH1
+# -------------------------------------------------------
+common:
+ header: '==========[ AuthMeReloaded ]=========='
+ optional: '可选'
+ hasPermission: '您拥有权限去使用这个指令'
+ noPermission: '您没有权限使用这个指令'
+ default: '默认'
+ result: '您的权限'
+ defaultPermissions:
+ notAllowed: '任何人不能使用'
+ opOnly: 'OP拥有此权限'
+ allowed: '所有人都可以使用'
+
+
+section:
+ command: '指令'
+ description: '功能'
+ detailedDescription: '功能详情'
+ arguments: '参数'
+ permissions: '权限'
+ alternatives: '别名'
+ children: '子命令'
+
+
+commands:
+ authme.register:
+ description: '注册一个玩家'
+ detailedDescription: '注册一个玩家'
+ arg1:
+ label: '玩家'
+ description: '玩家名称'
+ arg2:
+ label: '密码'
+ description: '密码'
+ authme.unregister:
+ description: '注销一个玩家'
+ detailedDescription: '注销一个玩家'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ authme.forcelogin:
+ description: '强制玩家重新登录'
+ detailedDescription: '强制使指定玩家重新登录'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ authme.password:
+ description: '改变某个玩家的密码'
+ detailedDescription: '改变某个玩家的密码'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ arg2:
+ label: '新密码'
+ description: '新密码'
+ authme.lastlogin:
+ description: '查看玩家最后登录时间'
+ detailedDescription: '查看玩家最后登录时间'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ authme.accounts:
+ description: '查看玩家IP下的账户'
+ detailedDescription: '查看玩家IP下的账户'
+ arg1:
+ label: '玩家或IP'
+ description: '玩家或IP'
+ authme.email:
+ description: '查看玩家的邮箱'
+ detailedDescription: '查看玩家的邮箱'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ authme.setemail:
+ description: '改变玩家的邮箱'
+ detailedDescription: '改变玩家的邮箱'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ arg2:
+ label: '邮箱'
+ description: '邮箱'
+ authme.getip:
+ description: '获取玩家IP'
+ detailedDescription: '获取玩家IP'
+ arg1:
+ label: '玩家'
+ description: '玩家'
+ authme.spawn:
+ description: '传送到AuthMe出生点'
+ detailedDescription: '传送到AuthMe出生点'
+ authme.setspawn:
+ description: '改变AuthMe出生点'
+ detailedDescription: '改变AuthMe出生点'
+ authme.firstspawn:
+ description: '传送到第一次进入游戏出生点'
+ detailedDescription: '传送到第一次进入游戏出生点'
+ authme.setfirstspawn:
+ description: '设置第一次进入游戏的出生点'
+ detailedDescription: '设置第一次进入游戏的出生点'
+ authme.purge:
+ description: '删除指定天数之前没登录的玩家登陆数据'
+ detailedDescription: '删除指定天数之前没登录的玩家登陆数据'
+ arg1:
+ label: '天数'
+ description: '天数'
+ arg2:
+ label: 'all'
+ description: '添加all到最后来清理最后登录为0的玩家'
+ authme.resetpos:
+ description: '重置玩家登出位置'
+ detailedDescription: '重置玩家登出位置'
+ arg1:
+ label: '玩家/*'
+ description: '玩家名称或所有玩家'
+ authme.purgebannedplayers:
+ description: '删除已经被封禁的玩家数据'
+ detailedDescription: '删除已经被封禁的玩家数据'
+ authme.switchantibot:
+ description: '改变AntiBot的状态'
+ detailedDescription: '改变AntiBot的状态'
+ arg1:
+ label: '开关'
+ description: '选项: ON/OFF'
+ authme.reload:
+ description: '重载插件'
+ detailedDescription: '重载插件'
+ authme.version:
+ description: '查看版本信息'
+ detailedDescription: '查看AuthmeReload版本,开发者,贡献者和许可'
+ authme.converter:
+ description: '转换数据命令'
+ detailedDescription: '转换数据命令'
+ arg1:
+ label: '类型'
+ description: '转换类型:xauth/crazylogin/rakamak/royalauth/vauth/sqliteToSql/mysqlToSqlite'
+ authme.messages:
+ description: '添加信息'
+ detailedDescription: '在语言文件夹中添加缺少的信息'
+ authme.help:
+ description: '查看帮助'
+ detailedDescription: '查看帮助'
+ arg1:
+ label: '子命令'
+ description: '查看的指令'
+ unregister:
+ description: '注销您的账户'
+ detailedDescription: '注销您的账户'
+ arg1:
+ label: '密码'
+ description: '密码'
+ changepassword:
+ description: '更改您的密码'
+ detailedDescription: '更改您的密码'
+ arg1:
+ label: '旧的密码'
+ description: '旧的密码'
+ arg2:
+ label: '新的密码'
+ description: '新的密码'
+ email:
+ description: '绑定邮箱或更改密码'
+ detailedDescription: '绑定邮箱或更改密码'
+ email.show:
+ description: '查看邮箱'
+ detailedDescription: '查看您的邮箱地址'
+ email.add:
+ description: '绑定邮箱'
+ detailedDescription: '为您的账户绑定邮箱'
+ arg1:
+ label: '邮箱'
+ description: '邮箱地址'
+ arg2:
+ label: '邮箱'
+ description: '重新输入邮箱地址'
+ email.change:
+ description: '改变邮箱地址'
+ detailedDescription: '更改您账户的邮箱地址'
+ arg1:
+ label: '旧邮箱'
+ description: '旧的邮箱地址'
+ arg2:
+ label: '新邮箱'
+ description: '新的邮箱地址'
+ email.recover:
+ description: '通过邮箱改变密码'
+ detailedDescription: '通过邮箱改变密码'
+ arg1:
+ label: '邮箱'
+ description: '邮箱地址'
+ arg2:
+ label: '验证码'
+ description: '验证码'
+ email.help:
+ description: '查看帮助'
+ detailedDescription: '查看邮箱帮助'
+ arg1:
+ label: '子命令'
+ description: '指令'
+ captcha:
+ description: '验证码'
+ detailedDescription: '验证码'
+ arg1:
+ label: '验证码'
+ description: '验证码'
+ captcha.help:
+ description: '查看验证码帮助'
+ detailedDescription: '查看验证码帮助'
+ arg1:
+ label: '子命令'
+ description: '指令'
diff --git a/src/main/resources/messages/messages_ko.yml b/src/main/resources/messages/messages_ko.yml
index bc2ee286..79f93fbc 100644
--- a/src/main/resources/messages/messages_ko.yml
+++ b/src/main/resources/messages/messages_ko.yml
@@ -16,7 +16,7 @@ password_error: '&c비밀번호가 일치하지 않습니다, 다시 확인해
password_error_nick: '&c자신의 닉네임을 비밀번호로 사용할 수 없습니다, 다른 비밀번호를 사용하세요...'
password_error_unsafe: '&c이 비밀번호는 안전하지 않습니다, 다른 비밀번호를 사용하세요...'
password_error_chars: '&4비밀번호에 잘못된 문자가 있습니다. 허용된 문자: REG_EX'
-pass_len: '&c너의 비번은 좆같이 길거나 좆같이 작아! 제발 다른걸 써줘!'
+pass_len: '&c비밀번호가 너무 짧거나 작습니다!'
# 로그인
usage_log: '&c사용법: /login <비밀번호>'
@@ -76,7 +76,7 @@ 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에전 이메일 주소가 잘못되었습니다, 다시 시도해보세요!'
+old_email_invalid: '&c예전 이메일 주소가 잘못되었습니다, 다시 시도해보세요!'
email_invalid: '&c이메일 주소가 잘못되었습니다, 다시 시도해보세요!'
email_added: '&2계정에 이메일 주소를 추가했습니다!'
email_confirm: '&c이메일 주소를 확인해주세요!'
diff --git a/src/main/resources/messages/messages_pl.yml b/src/main/resources/messages/messages_pl.yml
index 3d232ac3..3ccc03cf 100644
--- a/src/main/resources/messages/messages_pl.yml
+++ b/src/main/resources/messages/messages_pl.yml
@@ -1,11 +1,11 @@
# Registration
reg_msg: '&2Proszę się zarejestrować przy użyciu &6/register '
usage_reg: '&4Użycie: /register '
-reg_only: '&fTylko zarejestrowani użytkownicy maja do tego dostęp!'
+reg_only: '&fTylko zarejestrowani użytkownicy mają do tego dostęp!'
kicked_admin_registered: 'Administrator zarejestrował Ciebie, możesz się zalogować.'
registered: '&aPomyślnie zarejestrowany!'
-reg_disabled: '&4Rejestracja jest wyłączona'
-user_regged: '&4Gracz już jest zarejestrowany'
+reg_disabled: '&4Rejestracja jest wyłączona.'
+user_regged: '&4Gracz już jest zarejestrowany.'
# Password errors on registration
password_error: '&fHasło niepoprawne!'
@@ -18,29 +18,29 @@ pass_len: '&fTwoje hasło jest za krótkie lub za długie! Spróbuj ponownie...'
usage_log: '&cUżycie: /login hasło'
wrong_pwd: '&cNiepoprawne hasło.'
login: '&aHasło zaakceptowane!'
-login_msg: '&2Prosze się zalogować przy użyciu &6/login '
+login_msg: '&2Proszę się zalogować przy użyciu &6/login '
timeout: '&cUpłynął limit czasu zalogowania'
# Errors
-unknown_user: '&fGracz nie jest zarejestrowany'
+unknown_user: '&fGracz nie jest zarejestrowany.'
denied_command: '&cAby użyć tej komendy musisz się zalogować!'
denied_chat: '&cAby pisać na chacie musisz się zalogować!'
not_logged_in: '&4Nie jesteś zalogowany!'
tempban_max_logins: '&cZostałeś tymczasowo zbanowany za dużą liczbę nieudanych logowań!'
max_reg: '&cPrzekroczyłeś limit zarejestrowanych kont na serwerze &8(&e%reg_count/%max_acc %reg_names&8) &cdla twojego połączenia.'
no_perm: '&4Nie posiadasz wymaganych uprawnień.'
-error: '&fWystąpił błąd, prosimy napisać do administracji'
+error: '&fWystąpił błąd, prosimy skontaktować się z administracją serwera.'
kick_forvip: '&cGracz VIP dołączył do gry!'
# AntiBot
-kick_antibot: 'AntyBot został włączony, musisz poczekać minute przed dołączeniem do serwera'
+kick_antibot: '&cAntyBot został włączony, musisz poczekać minutę przed dołączeniem do serwera.'
antibot_auto_enabled: '&4[AntiBot] &aAntyBot włączony z powodu dużej liczby połączeń!'
antibot_auto_disabled: '&2[AntiBot] &aAntyBot zostanie wyłączony za &7%m &aminut!'
# Other messages
-unregistered: '&4Pomyslnie wyrejestrowany!'
-accounts_owned_self: 'Posiadasz %count kont:'
-accounts_owned_other: 'Gracz %name posiada %count kont:'
+unregistered: '&4Pomyślnie wyrejestrowany!'
+accounts_owned_self: '&7Posiadasz %count kont:'
+accounts_owned_other: '&7Gracz %name posiada %count kont:'
two_factor_create: '&2Twój sekretny kod to %code. Możesz zeskanować go tutaj: %url'
recovery_code_sent: 'Kod odzyskiwania hasła został wysłany na adres email przypisany do konta.'
recovery_code_incorrect: '&cKod odzyskiwania hasła jest błędny! &4Pozostałe próby: %count.'
@@ -66,8 +66,8 @@ country_banned: '&4Ten kraj jest zbanowany na tym serwerze'
not_owner_error: '&cNie jesteś właścicielem tego konta, wybierz inny nick!'
kick_fullserver: '&cSerwer jest teraz zapełniony, przepraszamy!'
same_nick: '&fTen nick już gra na serwerze!'
-invalid_name_case: 'Powinieneś dołączyć do serwera z nicku %valid, a nie %invalid.'
-same_ip_online: 'Gracz z takim samym adresem ip jest aktualnie w grze!'
+invalid_name_case: '&cPowinieneś dołączyć do serwera z nicku %valid, a nie %invalid.'
+same_ip_online: '&cGracz z takim samym adresem ip jest aktualnie w grze!'
# Email
usage_email_add: '&fWpisz: /email add '
diff --git a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
index 7f26cef5..a8b3a0fa 100644
--- a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
+++ b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
@@ -55,7 +55,7 @@ public class ClassesConsistencyTest {
/** Classes excluded from the field visibility test. */
private static final Set> CLASSES_EXCLUDED_FROM_VISIBILITY_TEST = ImmutableSet.of(
Whirlpool.class, // not our implementation, so we don't touch it
- Columns.class // uses non-final String constants, which is safe
+ Columns.class // uses non-static String constants, which is safe
);
/**
diff --git a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java
index c0b4771c..9d1f757e 100644
--- a/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java
+++ b/src/test/java/fr/xephi/authme/command/CommandUtilsTest.java
@@ -22,7 +22,7 @@ public class CommandUtilsTest {
@BeforeClass
public static void setUpTestCommands() {
- commands = TestCommandsUtil.generateCommands();
+ commands = Collections.unmodifiableCollection(TestCommandsUtil.generateCommands());
}
@Test
@@ -49,10 +49,6 @@ public class CommandUtilsTest {
assertThat(commandPath, equalTo("/authme help"));
}
-
- // ------
- // min / max arguments
- // ------
@Test
public void shouldComputeMinAndMaxOnEmptyCommand() {
// given
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/BackupCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/BackupCommandTest.java
new file mode 100644
index 00000000..2e1e0b5e
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/BackupCommandTest.java
@@ -0,0 +1,39 @@
+package fr.xephi.authme.command.executable.authme;
+
+import fr.xephi.authme.service.BackupService;
+import org.bukkit.command.CommandSender;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Test for {@link BackupCommand}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BackupCommandTest {
+
+ @InjectMocks
+ private BackupCommand command;
+
+ @Mock
+ private BackupService backupService;
+
+ @Test
+ public void shouldStartBackup() {
+ // given
+ CommandSender sender = mock(CommandSender.class);
+
+ // when
+ command.executeCommand(sender, Collections.emptyList());
+
+ // then
+ verify(backupService).doBackup(BackupService.BackupCause.COMMAND, sender);
+ }
+}
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java
new file mode 100644
index 00000000..dee5bdb7
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java
@@ -0,0 +1,116 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import com.google.common.cache.LoadingCache;
+import fr.xephi.authme.ReflectionTestUtils;
+import fr.xephi.authme.data.auth.PlayerAuth;
+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.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 org.junit.Before;
+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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Test for {@link DataStatistics}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DataStatisticsTest {
+
+ @InjectMocks
+ private DataStatistics dataStatistics;
+
+ @Mock
+ private DataSource dataSource;
+ @Mock
+ private PlayerCache playerCache;
+ @Mock
+ private LimboService limboService;
+ @Mock
+ private SingletonStore