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 singletonStore; + + @Before + public void setUpLimboCacheMap() { + Map limboMap = new HashMap<>(); + limboMap.put("test", mock(LimboPlayer.class)); + ReflectionTestUtils.setField(LimboService.class, limboService, "entries", limboMap); + } + + @Test + public void shouldOutputStatistics() { + // given + CommandSender sender = mock(CommandSender.class); + given(singletonStore.retrieveAllOfType()).willReturn(mockListOfSize(Object.class, 7)); + given(singletonStore.retrieveAllOfType(Reloadable.class)).willReturn(mockListOfSize(Reloadable.class, 4)); + given(singletonStore.retrieveAllOfType(SettingsDependent.class)).willReturn(mockListOfSize(SettingsDependent.class, 3)); + given(singletonStore.retrieveAllOfType(HasCleanup.class)).willReturn(mockListOfSize(HasCleanup.class, 2)); + given(dataSource.getAccountsRegistered()).willReturn(219); + given(playerCache.getLogged()).willReturn(12); + + + // when + dataStatistics.execute(sender, Collections.emptyList()); + + // then + ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, atLeastOnce()).sendMessage(stringCaptor.capture()); + assertThat(stringCaptor.getAllValues(), containsInAnyOrder( + "Singleton Java classes: 7 (Reloadable: 4 / SettingsDependent: 3 / HasCleanup: 2)", + "LimboPlayers in memory: 1", + "Total players in DB: 219", + "PlayerCache size: 12 (= logged in players)")); + } + + @Test + public void shouldOutputCachedDataSourceStatistics() { + // given + CacheDataSource cacheDataSource = mock(CacheDataSource.class); + LoadingCache> cache = mock(LoadingCache.class); + given(cache.size()).willReturn(11L); + given(cacheDataSource.getCachedAuths()).willReturn(cache); + ReflectionTestUtils.setField(DataStatistics.class, dataStatistics, "dataSource", cacheDataSource); + CommandSender sender = mock(CommandSender.class); + + // when + dataStatistics.execute(sender, Collections.emptyList()); + + // then + ArgumentCaptor stringCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, atLeastOnce()).sendMessage(stringCaptor.capture()); + assertThat(stringCaptor.getAllValues(), hasItem("Cached PlayerAuth objects: 11")); + } + + private static List mockListOfSize(Class mockClass, int size) { + T mock = mock(mockClass); + List mocks = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + mocks.add(mock); + } + return mocks; + } +} 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 e8443260..bd4b42e0 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 @@ -5,6 +5,7 @@ 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.Before; import org.junit.Test; import java.util.HashMap; @@ -12,6 +13,7 @@ import java.util.Map; import java.util.function.Function; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; @@ -21,6 +23,11 @@ import static org.mockito.Mockito.mock; */ public class DebugSectionUtilsTest { + @Before + public void initMockLogger() { + TestHelper.setupLogger(); + } + @Test public void shouldFormatLocation() { // given / when @@ -66,4 +73,20 @@ public class DebugSectionUtilsTest { // then assertThat(map, sameInstance(limboMap)); } + + @Test + public void shouldHandleErrorGracefully() { + // given + LimboService limboService = mock(LimboService.class); + Map limboMap = new HashMap<>(); + ReflectionTestUtils.setField(LimboService.class, limboService, "entries", limboMap); + + // when + Object result = DebugSectionUtils.applyToLimboPlayersMap(limboService, map -> { + throw new IllegalStateException(); + }); + + // then + assertThat(result, nullValue()); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java index e43566f2..cee38404 100644 --- a/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommandTest.java @@ -19,6 +19,8 @@ import java.util.Arrays; import java.util.Collections; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; @@ -106,11 +108,16 @@ public class ChangePasswordCommandTest { verify(management).performPasswordChange(player, oldPass, newPass); } + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_CHANGE_PASSWORD)); + } + private Player initPlayerWithName(String name, boolean loggedIn) { Player player = mock(Player.class); when(player.getName()).thenReturn(name); when(playerCache.isAuthenticated(name)).thenReturn(loggedIn); return player; } - } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java index 75c10e7a..9a039b50 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/AddEmailCommandTest.java @@ -15,6 +15,8 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; +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; @@ -73,4 +75,9 @@ public class AddEmailCommandTest { verify(commandService).send(sender, MessageKey.CONFIRM_EMAIL_MESSAGE); } + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_ADD_EMAIL)); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java index 8876abbf..b4cb80a9 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/ChangeEmailCommandTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.email; +import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; @@ -10,9 +11,11 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import 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; @@ -36,7 +39,7 @@ public class ChangeEmailCommandTest { CommandSender sender = mock(BlockCommandSender.class); // when - command.executeCommand(sender, new ArrayList()); + command.executeCommand(sender, Collections.emptyList()); // then verifyZeroInteractions(management); @@ -54,4 +57,9 @@ public class ChangeEmailCommandTest { verify(management).performChangeEmail(sender, "new.mail@example.org", "old_mail@example.org"); } + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_CHANGE_EMAIL)); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/email/EmailBaseCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/EmailBaseCommandTest.java new file mode 100644 index 00000000..7ca0aff9 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/email/EmailBaseCommandTest.java @@ -0,0 +1,49 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.command.CommandMapper; +import fr.xephi.authme.command.FoundCommandResult; +import fr.xephi.authme.command.help.HelpProvider; +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.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link EmailBaseCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class EmailBaseCommandTest { + + @InjectMocks + private EmailBaseCommand command; + + @Mock + private HelpProvider helpProvider; + @Mock + private CommandMapper commandMapper; + + @Test + public void shouldDisplayHelp() { + // given + CommandSender sender = mock(CommandSender.class); + FoundCommandResult result = mock(FoundCommandResult.class); + given(commandMapper.mapPartsToCommand(eq(sender), anyList())).willReturn(result); + + // when + command.executeCommand(sender, Collections.emptyList()); + + // then + verify(commandMapper).mapPartsToCommand(sender, Collections.singletonList("email")); + verify(helpProvider).outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java index b6c08eaa..9ca0bc09 100644 --- a/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/email/RecoverEmailCommandTest.java @@ -22,6 +22,8 @@ import org.mockito.Mock; import java.util.Collections; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -208,4 +210,10 @@ public class RecoverEmailCommandTest { verify(dataSource).getEmail(name); verify(recoveryService).generateAndSendNewPassword(sender, email); } + + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_RECOVER_EMAIL)); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java index df6eb7d8..38307f93 100644 --- a/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/login/LoginCommandTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.login; +import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.process.Management; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; @@ -13,6 +14,8 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Collections; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -57,4 +60,9 @@ public class LoginCommandTest { verify(management).performLogin(eq(sender), eq("password")); } + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_LOGIN)); + } } diff --git a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java index d4560640..2adc66d5 100644 --- a/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/unregister/UnregisterCommandTest.java @@ -15,6 +15,8 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Collections; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -87,4 +89,9 @@ public class UnregisterCommandTest { verify(sender).sendMessage(argThat(containsString("/authme unregister "))); } + @Test + public void shouldDefineArgumentMismatchMessage() { + // given / when / then + assertThat(command.getArgumentsMismatchMessage(), equalTo(MessageKey.USAGE_UNREGISTER)); + } } diff --git a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java index ea0f372a..bef86fca 100644 --- a/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/help/HelpMessagesServiceTest.java @@ -10,6 +10,7 @@ import fr.xephi.authme.message.MessageFileHandlerProvider; import fr.xephi.authme.permission.DefaultPermission; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import java.util.Collection; @@ -19,10 +20,13 @@ import static fr.xephi.authme.TestHelper.getJarFile; import static fr.xephi.authme.command.TestCommandsUtil.getCommandWithLabel; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +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.BDDMockito.given; +import static org.mockito.Mockito.verify; /** * Test for {@link HelpMessagesService}. @@ -46,6 +50,17 @@ public class HelpMessagesServiceTest { given(messageFileHandlerProvider.initializeHandler(any(Function.class))).willReturn(handler); } + @Test + @SuppressWarnings("unchecked") + public void shouldUseExistingFileAsTextFile() { + // given / when / then + ArgumentCaptor> functionCaptor = ArgumentCaptor.forClass(Function.class); + verify(messageFileHandlerProvider).initializeHandler(functionCaptor.capture()); + Function helpFilePathBuilder = functionCaptor.getValue(); + String defaultFilePath = helpFilePathBuilder.apply("en"); + assertThat(getClass().getClassLoader().getResource(defaultFilePath), not(nullValue())); + } + @Test public void shouldReturnLocalizedCommand() { // given diff --git a/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java index 8c22cc75..1f56cdfd 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/AllowFlightRestoreTypeTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; /** * Test for {@link AllowFlightRestoreType}. @@ -64,6 +65,42 @@ public class AllowFlightRestoreTypeTest { verify(player2).setAllowFlight(false); } + @Test + public void shouldNotInteractWithPlayer() { + // given + LimboPlayer limboWithFly = newLimboWithAllowFlight(true); + LimboPlayer limboWithoutFly = newLimboWithAllowFlight(false); + Player player1 = mock(Player.class); + Player player2 = mock(Player.class); + + // when + AllowFlightRestoreType.NOTHING.restoreAllowFlight(player1, limboWithFly); + AllowFlightRestoreType.NOTHING.restoreAllowFlight(player2, limboWithoutFly); + + // then + verifyZeroInteractions(player1, player2); + } + + @Test + public void shouldRemoveFlightExceptForNothingType() { + // given + AllowFlightRestoreType noInteractionType = AllowFlightRestoreType.NOTHING; + + for (AllowFlightRestoreType type : AllowFlightRestoreType.values()) { + Player player = mock(Player.class); + + // when + type.processPlayer(player); + + // then + if (type == noInteractionType) { + verifyZeroInteractions(player); + } else { + verify(player).setAllowFlight(false); + } + } + } + private static LimboPlayer newLimboWithAllowFlight(boolean allowFlight) { LimboPlayer limbo = mock(LimboPlayer.class); given(limbo.isCanFly()).willReturn(allowFlight); 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 0889751f..81feaa8e 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -84,6 +84,7 @@ public class LimboServiceTest { given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); given(permissionsManager.hasGroupSupport()).willReturn(true); given(permissionsManager.getGroups(player)).willReturn(Collections.singletonList("permgrwp")); + given(settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)).willReturn(AllowFlightRestoreType.ENABLE); // when limboService.createLimboPlayer(player, true); @@ -114,6 +115,7 @@ public class LimboServiceTest { Location playerLoc = mock(Location.class); given(spawnLoader.getPlayerLocationOrSpawn(player)).willReturn(playerLoc); given(permissionsManager.hasGroupSupport()).willReturn(false); + given(settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)).willReturn(AllowFlightRestoreType.RESTORE); // when limboService.createLimboPlayer(player, false); @@ -143,6 +145,7 @@ public class LimboServiceTest { LimboPlayer existingLimbo = mock(LimboPlayer.class); getLimboMap().put("carlos", existingLimbo); Player player = newPlayer("Carlos"); + given(settings.getProperty(LimboSettings.RESTORE_ALLOW_FLIGHT)).willReturn(AllowFlightRestoreType.ENABLE); // when limboService.createLimboPlayer(player, false); diff --git a/src/test/java/fr/xephi/authme/security/crypts/TwoFactorTest.java b/src/test/java/fr/xephi/authme/security/crypts/TwoFactorTest.java index 9b2dddff..cdc25267 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/TwoFactorTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/TwoFactorTest.java @@ -25,7 +25,7 @@ public class TwoFactorTest { String secret = "3AK6Y4KWGRLJMEQW"; // when - String url = TwoFactor.getQRBarcodeURL(user, host, secret); + String url = TwoFactor.getQrBarcodeUrl(user, host, secret); // then String expected = "https://www.google.com/chart?chs=130x130&chld=M%7C0&cht=qr" diff --git a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java index 11a0f253..c62b0b40 100644 --- a/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/MigrationServiceTest.java @@ -28,7 +28,6 @@ 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}. diff --git a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java index e1795dc1..ebf92726 100644 --- a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java +++ b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java @@ -178,6 +178,19 @@ public class CommandManagerTest { verifyZeroInteractions(bukkitService, geoIpService); } + @Test + public void shouldExecuteCommandOnUnregister() { + // given + copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.incomplete.yml"); + initManager(); + + // when + manager.runCommandsOnUnregister(player); + + // then + verify(bukkitService).dispatchConsoleCommand("msg Bobby sad to see you go!"); + } + @Test public void shouldHaveHiddenConstructorInSettingsHolderClass() { // given / when / then diff --git a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java index 9d65047c..a20210bb 100644 --- a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java @@ -75,6 +75,20 @@ public class FileUtilsTest { assertThat(file.exists(), equalTo(false)); } + @Test + public void shouldReturnFalseForParentInvalidParentFolders() throws IOException { + // given + File folder = temporaryFolder.newFolder(); + new File(folder, "hello").createNewFile(); + File fileToCreate = new File(folder, "hello/test"); + + // when + boolean result = FileUtils.copyFileFromResource(fileToCreate, "welcome.txt"); + + // then + assertThat(result, equalTo(false)); + } + @Test public void shouldPurgeDirectory() throws IOException { // given @@ -137,6 +151,36 @@ public class FileUtilsTest { assertThat(result, equalTo("path" + File.separator + "to" + File.separator + "test-file.txt")); } + @Test + public void shouldCreateDirectory() throws IOException { + // given + File root = temporaryFolder.newFolder(); + File dir = new File(root, "folder/folder2/myFolder"); + + // when + boolean result = FileUtils.createDirectory(dir); + + // then + assertThat(result, equalTo(true)); + assertThat(dir.exists(), equalTo(true)); + assertThat(dir.isDirectory(), equalTo(true)); + } + + @Test + public void shouldReturnFalseOnDirectoryCreateFail() throws IOException { + // given + File root = temporaryFolder.newFolder(); + File dirAsFile = new File(root, "file"); + dirAsFile.createNewFile(); + + // when + boolean result = FileUtils.createDirectory(dirAsFile); + + // then + assertThat(result, equalTo(false)); + assertThat(dirAsFile.isFile(), equalTo(true)); + } + @Test public void shouldHaveHiddenConstructor() { TestHelper.validateHasOnlyPrivateEmptyConstructor(FileUtils.class); diff --git a/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java b/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java index e71f69c1..d45c0a57 100644 --- a/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/InternetProtocolUtilsTest.java @@ -1,5 +1,6 @@ package fr.xephi.authme.util; +import fr.xephi.authme.TestHelper; import org.junit.Test; import static org.junit.Assert.assertThat; @@ -19,4 +20,10 @@ public class InternetProtocolUtilsTest { assertThat(InternetProtocolUtils.isLocalAddress("192.168.0.1"), equalTo(true)); assertThat(InternetProtocolUtils.isLocalAddress("94.32.34.5"), equalTo(false)); } + + @Test + public void shouldHavePrivateConstructor() { + // given / when / then + TestHelper.validateHasOnlyPrivateEmptyConstructor(InternetProtocolUtils.class); + } } diff --git a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java index b5200b01..556a604c 100644 --- a/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/RandomStringUtilsTest.java @@ -53,7 +53,7 @@ public class RandomStringUtilsTest { // when / then for (int length : lengths) { - String result = RandomStringUtils.generateHex(length); + String result = RandomStringUtils.generateLowerUpper(length); assertThat("Result '" + result + "' should have length " + length, result.length(), equalTo(length)); assertThat("Result '" + result + "' should only have characters a-z, A-Z, 0-9", diff --git a/src/test/java/fr/xephi/authme/util/UtilsTest.java b/src/test/java/fr/xephi/authme/util/UtilsTest.java index ce8675f4..87fc308c 100644 --- a/src/test/java/fr/xephi/authme/util/UtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/UtilsTest.java @@ -1,16 +1,22 @@ package fr.xephi.authme.util; import fr.xephi.authme.TestHelper; +import org.bukkit.ChatColor; 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.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.logging.Logger; import java.util.regex.Pattern; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -99,6 +105,68 @@ public class UtilsTest { verifyZeroInteractions(sender); } + @Test + public void shouldCheckIfCollectionIsEmpty() { + // given + List emptyList = Collections.emptyList(); + Collection nonEmptyColl = Arrays.asList(3, 4, 5); + + // when / then + assertThat(Utils.isCollectionEmpty(emptyList), equalTo(true)); + assertThat(Utils.isCollectionEmpty(nonEmptyColl), equalTo(false)); + assertThat(Utils.isCollectionEmpty(null), equalTo(true)); + } + + @Test + public void shouldReturnCoreCount() { + // given / when / then + assertThat(Utils.getCoreCount(), greaterThan(0)); + } + + @Test + public void shouldLogAndSendWarning() { + // given + Logger logger = TestHelper.setupLogger(); + String message = "Error while performing action"; + CommandSender sender = mock(CommandSender.class); + + // when + Utils.logAndSendWarning(sender, message); + + // then + verify(logger).warning(message); + verify(sender).sendMessage(ChatColor.RED + message); + } + + @Test + public void shouldLogWarningAndNotSendToConsoleSender() { + // given + Logger logger = TestHelper.setupLogger(); + String message = "Error while performing action"; + CommandSender sender = mock(ConsoleCommandSender.class); + + // when + Utils.logAndSendWarning(sender, message); + + // then + verify(logger).warning(message); + verifyZeroInteractions(sender); + } + + @Test + public void shouldLogWarningAndHandleNullCommandSender() { + // given + Logger logger = TestHelper.setupLogger(); + String message = "Error while performing action"; + CommandSender sender = null; + + // when + Utils.logAndSendWarning(sender, message); + + // then + verify(logger).warning(message); + } + @Test public void shouldCheckIfClassIsLoaded() { // given / when / then diff --git a/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java b/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java index 0a1d3fa5..646d8d37 100644 --- a/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java +++ b/src/test/java/tools/docs/hashmethods/EncryptionMethodInfoGatherer.java @@ -30,7 +30,7 @@ import static org.mockito.Mockito.when; */ public class EncryptionMethodInfoGatherer { - private final static Set> RELEVANT_ANNOTATIONS = + private static final Set> RELEVANT_ANNOTATIONS = ImmutableSet.of(HasSalt.class, Recommendation.class, AsciiRestricted.class); private static Injector injector = createInitializer(); @@ -104,6 +104,9 @@ public class EncryptionMethodInfoGatherer { /** * Returns the super class of the given encryption method if it is also of EncryptionMethod type. * (Anything beyond EncryptionMethod is not of interest.) + * + * @param methodClass the class to process + * @return the super class of the given class if it is also an EncryptionMethod type, otherwise null */ private static Class getSuperClass(Class methodClass) { Class zuper = methodClass.getSuperclass(); diff --git a/src/test/java/tools/filegeneration/GeneratePluginYml.java b/src/test/java/tools/filegeneration/GeneratePluginYml.java index 8fdc779d..04dba91e 100644 --- a/src/test/java/tools/filegeneration/GeneratePluginYml.java +++ b/src/test/java/tools/filegeneration/GeneratePluginYml.java @@ -140,10 +140,15 @@ public class GeneratePluginYml implements AutoToolTask { } private static String buildUsage(CommandDescription command) { - if (!command.getArguments().isEmpty()) { - return CommandUtils.buildSyntax(command); - } final String commandStart = "/" + command.getLabels().get(0); + if (!command.getArguments().isEmpty()) { + // Command has arguments, so generate something like /authme register + final String arguments = command.getArguments().stream() + .map(CommandUtils::formatArgument) + .collect(Collectors.joining(" ")); + return commandStart + " " + arguments; + } + // Argument-less command, list all children: /authme register|login|firstspawn|spawn|... String usage = commandStart + " " + command.getChildren() .stream() .filter(cmd -> !cmd.getLabels().contains("help")) diff --git a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml index a1e9060f..8bde4aab 100644 --- a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml +++ b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.incomplete.yml @@ -18,3 +18,7 @@ doesNotExist: wrongEntry: command: 'should be ignored' executor: PLAYER +onUnregister: + farewell: + command: 'msg %p sad to see you go!' + executor: CONSOLE