diff --git a/docs/commands.md b/docs/commands.md index 344b643d..3d978090 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,5 +1,5 @@ - + ## AuthMe Commands You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >` @@ -32,8 +32,10 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.admin.firstspawn` - **/authme setfirstspawn**: Change the first player's spawn to your current position.
Requires `authme.admin.setfirstspawn` -- **/authme purge** <days> [all]: Purge old AuthMeReloaded data longer than the specified amount of days ago. +- **/authme purge** <days> [all]: Purge old AuthMeReloaded data longer than the specified number of days ago.
Requires `authme.admin.purge` +- **/authme backup**: Creates a backup of the registered users. +
Requires `authme.admin.backup` - **/authme resetpos** <player/*>: Purge the last know position of the specified player or all of them.
Requires `authme.admin.purgelastpos` - **/authme purgebannedplayers**: Purge all AuthMeReloaded data for banned players. @@ -47,8 +49,8 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.admin.converter` - **/authme messages**: Adds missing messages to the current messages file.
Requires `authme.admin.updatemessages` -- **/authme debug** [child] [params]: Allows various operations for debugging. -
Requires `authme.debug` +- **/authme debug** [child] [arg] [arg]: Allows various operations for debugging. +
Requires `authme.debug.command` - **/authme help** [query]: View detailed help for /authme commands. - **/login** <password>: Command to log in using AuthMeReloaded.
Requires `authme.player.login` @@ -82,6 +84,7 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.player.captcha` - **/captcha help** [query]: View detailed help for /captcha commands. + --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:32 CET 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:29:43 CEST 2017 diff --git a/docs/config.md b/docs/config.md index 9c8a52bd..8da66c1a 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, @@ -481,13 +481,13 @@ limbo: # See above for a description of the values. restoreWalkSpeed: 'MAX_RESTORE' BackupSystem: - # Enable or disable automatic backup + # General configuration for backups: if false, no backups are possible ActivateBackup: false - # Set backup at every start of server + # Create backup at every start of server OnServerStart: false - # Set backup at every stop of server + # Create backup at every stop of server OnServerStop: true - # Windows only mysql installation Path + # Windows only: MySQL installation path MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\' Converter: Rakamak: @@ -507,4 +507,4 @@ To change settings on a running server, save your changes to config.yml and use --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Mar 28 21:48:52 CEST 2017 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:30:08 CEST 2017 diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md index fb640820..4bf2c011 100644 --- a/docs/permission_nodes.md +++ b/docs/permission_nodes.md @@ -1,5 +1,5 @@ - + ## AuthMe Permission Nodes The following are the permission nodes that are currently supported by the latest dev builds. @@ -7,6 +7,7 @@ The following are the permission nodes that are currently supported by the lates - **authme.admin.*** – Give access to all admin commands. - **authme.admin.accounts** – Administrator command to see all accounts associated with a user. - **authme.admin.antibotmessages** – Permission to see Antibot messages. +- **authme.admin.backup** – Allows to use the backup command. - **authme.admin.changemail** – Administrator command to set or change the email address of a user. - **authme.admin.changepassword** – Administrator command to change the password of a user. - **authme.admin.converter** – Administrator command to convert old or other data to AuthMe data. @@ -31,6 +32,16 @@ The following are the permission nodes that are currently supported by the lates - **authme.bypassantibot** – Permission node to bypass AntiBot protection. - **authme.bypassforcesurvival** – Permission for users to bypass force-survival mode. - **authme.bypasspurge** – Permission to bypass the purging process. +- **authme.debug.command** – General permission to use the /authme debug command. +- **authme.debug.country** – Permission to use the country lookup section. +- **authme.debug.db** – Permission to view data from the database. +- **authme.debug.group** – Permission to view permission groups. +- **authme.debug.limbo** – Permission to use the limbo data viewer. +- **authme.debug.mail** – Permission to use the test email sender. +- **authme.debug.perm** – Permission to use the permission checker. +- **authme.debug.spawn** – Permission to view spawn information. +- **authme.debug.stats** – Permission to use the stats section. +- **authme.debug.valid** – Permission to use sample validation. - **authme.player.*** – Permission to use all player (non-admin) commands. - **authme.player.canbeforced** – Permission for users a login can be forced to. - **authme.player.captcha** – Command permission to use captcha. @@ -49,4 +60,4 @@ The following are the permission nodes that are currently supported by the lates --- -This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 23 15:38:58 CEST 2016 +This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:32:06 CEST 2017 diff --git a/pom.xml b/pom.xml index 52115a89..7dfc66f3 100644 --- a/pom.xml +++ b/pom.xml @@ -568,7 +568,7 @@ com.comphenix.protocol ProtocolLib-API - 4.2.0 + 4.2.1 provided diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index c381457e..96313819 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -72,6 +72,7 @@ public class AuthMe extends JavaPlugin { private DataSource database; private BukkitService bukkitService; private Injector injector; + private BackupService backupService; /** * Constructor. @@ -154,7 +155,7 @@ public class AuthMe extends JavaPlugin { } // Do a backup on start - new BackupService(this, settings).doBackup(BackupService.BackupCause.START); + backupService.doBackup(BackupService.BackupCause.START); // Set up Metrics OnStartupTasks.sendMetrics(this, settings); @@ -257,6 +258,7 @@ public class AuthMe extends JavaPlugin { permsMan = injector.getSingleton(PermissionsManager.class); bukkitService = injector.getSingleton(BukkitService.class); commandHandler = injector.getSingleton(CommandHandler.class); + backupService = injector.getSingleton(BackupService.class); // Trigger construction of API classes; they will keep track of the singleton injector.getSingleton(fr.xephi.authme.api.v3.AuthMeApi.class); @@ -352,8 +354,8 @@ public class AuthMe extends JavaPlugin { } // Do backup on stop if enabled - if (settings != null) { - new BackupService(this, settings).doBackup(BackupService.BackupCause.STOP); + if (backupService != null) { + backupService.doBackup(BackupService.BackupCause.STOP); } // Wait for tasks and close data source diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index dd27c279..70dfe734 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import fr.xephi.authme.command.executable.HelpCommand; import fr.xephi.authme.command.executable.authme.AccountsCommand; import fr.xephi.authme.command.executable.authme.AuthMeCommand; +import fr.xephi.authme.command.executable.authme.BackupCommand; import fr.xephi.authme.command.executable.authme.ChangePasswordAdminCommand; import fr.xephi.authme.command.executable.authme.ConverterCommand; import fr.xephi.authme.command.executable.authme.FirstSpawnCommand; @@ -39,8 +40,8 @@ import fr.xephi.authme.command.executable.logout.LogoutCommand; import fr.xephi.authme.command.executable.register.RegisterCommand; import fr.xephi.authme.command.executable.unregister.UnregisterCommand; import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.DebugSectionPermissions; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.permission.PlayerStatePermission; import java.util.Arrays; import java.util.Collection; @@ -226,13 +227,23 @@ public class CommandInitializer { .parent(AUTHME_BASE) .labels("purge", "delete") .description("Purge old data") - .detailedDescription("Purge old AuthMeReloaded data longer than the specified amount of days ago.") + .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(); + // 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) @@ -312,10 +323,9 @@ public class CommandInitializer { .description("Debug features") .detailedDescription("Allows various operations for debugging.") .withArgument("child", "The child to execute", true) - .withArgument(".", "meaning varies", true) - .withArgument(".", "meaning varies", true) - .withArgument(".", "meaning varies", true) - .permission(PlayerStatePermission.DEBUG_COMMAND) + .withArgument("arg", "argument (depends on debug section)", true) + .withArgument("arg", "argument (depends on debug section)", true) + .permission(DebugSectionPermissions.DEBUG_COMMAND) .executableCommand(DebugCommand.class) .register(); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java new file mode 100644 index 00000000..0e72fb60 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.command.executable.authme; + +import fr.xephi.authme.command.ExecutableCommand; +import fr.xephi.authme.service.BackupService; +import fr.xephi.authme.service.BackupService.BackupCause; +import org.bukkit.command.CommandSender; + +import javax.inject.Inject; +import java.util.List; + +/** + * Command to perform a backup. + */ +public class BackupCommand implements ExecutableCommand { + + @Inject + private BackupService backupService; + + @Override + public void executeCommand(CommandSender sender, List arguments) { + backupService.doBackup(BackupCause.COMMAND, sender); + } +} diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java index 05de8ab1..781b8392 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java @@ -2,6 +2,8 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.properties.ProtectionSettings; @@ -54,6 +56,11 @@ class CountryLookup implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.COUNTRY_LOOKUP; + } + private void outputInfoForIpAddr(CommandSender sender, String ipAddr) { sender.sendMessage("IP '" + ipAddr + "' maps to country '" + geoIpService.getCountryCode(ipAddr) + "' (" + geoIpService.getCountryName(ipAddr) + ")"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java index 00540e8b..22ee211f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java @@ -8,6 +8,8 @@ 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 fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import org.bukkit.command.CommandSender; import javax.inject.Inject; @@ -52,6 +54,11 @@ class DataStatistics implements DebugSection { outputInjectorStats(sender); } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.DATA_STATISTICS; + } + private void outputDatabaseStats(CommandSender sender) { sender.sendMessage("Total players in DB: " + dataSource.getAccountsRegistered()); if (dataSource instanceof CacheDataSource) { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java index fc7cd429..e8976287 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java @@ -3,6 +3,8 @@ package fr.xephi.authme.command.executable.authme.debug; import com.google.common.collect.ImmutableSet; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.permission.PermissionsManager; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import javax.inject.Inject; @@ -24,27 +26,48 @@ public class DebugCommand implements ExecutableCommand { @Inject private Factory debugSectionFactory; + @Inject + private PermissionsManager permissionsManager; + private Map sections; @Override public void executeCommand(CommandSender sender, List arguments) { - DebugSection debugSection = getDebugSection(arguments); + DebugSection debugSection = findDebugSection(arguments); if (debugSection == null) { - sender.sendMessage("Available sections:"); - getSections().values() - .forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription())); + sendAvailableSections(sender); } else { - debugSection.execute(sender, arguments.subList(1, arguments.size())); + executeSection(debugSection, sender, arguments); } } - private DebugSection getDebugSection(List arguments) { + private DebugSection findDebugSection(List arguments) { if (arguments.isEmpty()) { return null; } return getSections().get(arguments.get(0).toLowerCase()); } + private void sendAvailableSections(CommandSender sender) { + sender.sendMessage("Sections available to you:"); + long availableSections = getSections().values().stream() + .filter(section -> permissionsManager.hasPermission(sender, section.getRequiredPermission())) + .peek(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription())) + .count(); + + if (availableSections == 0) { + sender.sendMessage(ChatColor.RED + "You don't have permission to view any debug section"); + } + } + + private void executeSection(DebugSection section, CommandSender sender, List arguments) { + if (permissionsManager.hasPermission(sender, section.getRequiredPermission())) { + section.execute(sender, arguments.subList(1, arguments.size())); + } else { + sender.sendMessage(ChatColor.RED + "You don't have permission for this section. See /authme debug"); + } + } + // Lazy getter private Map getSections() { if (sections == null) { diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java index 1f038983..155520c4 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java @@ -1,5 +1,6 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.permission.PermissionNode; import org.bukkit.command.CommandSender; import java.util.List; @@ -27,4 +28,9 @@ interface DebugSection { */ void execute(CommandSender sender, List arguments); + /** + * @return permission required to run this section + */ + PermissionNode getRequiredPermission(); + } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java index 436ef180..4d2bacf1 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java @@ -2,6 +2,7 @@ package fr.xephi.authme.command.executable.authme.debug; import com.google.common.collect.ImmutableList; import fr.xephi.authme.permission.AdminPermission; +import fr.xephi.authme.permission.DebugSectionPermissions; import fr.xephi.authme.permission.DefaultPermission; import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsManager; @@ -25,8 +26,8 @@ import java.util.function.BiFunction; */ class HasPermissionChecker implements DebugSection { - static final List> PERMISSION_NODE_CLASSES = - ImmutableList.of(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class); + static final List> PERMISSION_NODE_CLASSES = ImmutableList.of( + AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class, DebugSectionPermissions.class); @Inject private PermissionsManager permissionsManager; @@ -69,6 +70,11 @@ class HasPermissionChecker implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.HAS_PERMISSION_CHECK; + } + /** * Performs a permission check and informs the given sender of the result. {@code permissionChecker} is the * permission check to perform with the given {@code node} and the {@code player}. @@ -90,7 +96,6 @@ class HasPermissionChecker implements DebugSection { sender.sendMessage(ChatColor.DARK_RED + "Check failed: player '" + player.getName() + "' does NOT have permission '" + node + "'"); } - } /** diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java index 47202506..d1131e0a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java @@ -3,6 +3,8 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.listener.FailedVerificationException; import fr.xephi.authme.listener.OnJoinVerifier; import fr.xephi.authme.message.Messages; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.service.ValidationService.ValidationResult; import org.bukkit.ChatColor; @@ -60,6 +62,11 @@ class InputValidator implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.INPUT_VALIDATOR; + } + private void displayUsageHint(CommandSender sender) { sender.sendMessage("You can define forbidden emails and passwords in your config.yml"); sender.sendMessage("This command allows you to test some of the values:"); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java index a8c62e68..b017d193 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java @@ -3,6 +3,8 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.data.limbo.persistence.LimboPersistence; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.service.BukkitService; import org.bukkit.ChatColor; @@ -71,6 +73,11 @@ class LimboPlayerViewer implements DebugSection { .sendEntry("Group", LimboPlayer::getGroup, permissionsManager::getPrimaryGroup); } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.LIMBO_PLAYER_VIEWER; + } + /** * Displays the info for the given LimboPlayer and Player to the provided CommandSender. */ diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java index a7bf19eb..0fd62d8d 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java @@ -1,5 +1,7 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionsManager; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -38,4 +40,9 @@ class PermissionGroups implements DebugSection { sender.sendMessage("Primary group is: " + permissionsManager.getGroups(player)); } } + + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.PERM_GROUPS; + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java index a985c827..f4a26ec8 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java @@ -2,6 +2,8 @@ package fr.xephi.authme.command.executable.authme.debug; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; @@ -50,6 +52,11 @@ class PlayerAuthViewer implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.PLAYER_AUTH_VIEWER; + } + /** * Outputs the PlayerAuth information to the given sender. * diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java index 0262055e..cfb6e0cd 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java @@ -1,5 +1,7 @@ package fr.xephi.authme.command.executable.authme.debug; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.SpawnLoader; @@ -49,6 +51,11 @@ class SpawnLocationViewer implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.SPAWN_LOCATION; + } + private void showGeneralInfo(CommandSender sender) { sender.sendMessage("Spawn priority: " + String.join(", ", settings.getProperty(RestrictionSettings.SPAWN_PRIORITY))); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java index ccbfde6f..4d3cfc07 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java @@ -4,6 +4,8 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceResult; import fr.xephi.authme.mail.SendMailSsl; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; import org.apache.commons.mail.EmailException; @@ -61,6 +63,11 @@ class TestEmailSender implements DebugSection { } } + @Override + public PermissionNode getRequiredPermission() { + return DebugSectionPermissions.TEST_EMAIL; + } + private String getEmail(CommandSender sender, List arguments) { if (arguments.isEmpty()) { DataSourceResult emailResult = dataSource.getEmail(sender.getName()); 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 bbacd6c5..64a93551 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 @@ -41,10 +41,7 @@ class DistributedFilesPersistenceHandler implements LimboPersistenceHandler { @Inject DistributedFilesPersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) { cacheFolder = new File(dataFolder, "playerdata"); - if (!cacheFolder.exists()) { - // TODO ljacqu 20170313: Create FileUtils#mkdirs - cacheFolder.mkdirs(); - } + FileUtils.createDirectory(cacheFolder); gson = new GsonBuilder() .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer()) diff --git a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java index 4257f0aa..a65dfbc2 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java @@ -8,6 +8,7 @@ import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.ConverterSettings; +import fr.xephi.authme.util.Utils; import org.bukkit.command.CommandSender; import javax.inject.Inject; @@ -85,8 +86,7 @@ public class RakamakConverter implements Converter { .build(); database.saveAuth(auth); } - ConsoleLogger.info("Rakamak database has been imported correctly"); - sender.sendMessage("Rakamak database has been imported correctly"); + Utils.logAndSendMessage(sender, "Rakamak database has been imported correctly"); } catch (IOException ex) { ConsoleLogger.logException("Can't open the rakamak database file! Does it exist?", ex); } diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java index 46d58072..4f0957a0 100644 --- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java +++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java @@ -113,7 +113,12 @@ public enum AdminPermission implements PermissionNode { /** * Permission to see the other accounts of the players that log in. */ - SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts"); + SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts"), + + /** + * Allows to use the backup command. + */ + BACKUP("authme.admin.backup"); /** * The permission node. diff --git a/src/main/java/fr/xephi/authme/permission/DebugSectionPermissions.java b/src/main/java/fr/xephi/authme/permission/DebugSectionPermissions.java new file mode 100644 index 00000000..ee2d0d7f --- /dev/null +++ b/src/main/java/fr/xephi/authme/permission/DebugSectionPermissions.java @@ -0,0 +1,58 @@ +package fr.xephi.authme.permission; + +/** + * Permissions for the debug sections (/authme debug). + */ +public enum DebugSectionPermissions implements PermissionNode { + + /** General permission to use the /authme debug command. */ + DEBUG_COMMAND("authme.debug.command"), + + /** Permission to use the country lookup section. */ + COUNTRY_LOOKUP("authme.debug.country"), + + /** Permission to use the stats section. */ + DATA_STATISTICS("authme.debug.stats"), + + /** Permission to use the permission checker. */ + HAS_PERMISSION_CHECK("authme.debug.perm"), + + /** Permission to use sample validation. */ + INPUT_VALIDATOR("authme.debug.valid"), + + /** Permission to use the limbo data viewer. */ + LIMBO_PLAYER_VIEWER("authme.debug.limbo"), + + /** Permission to view permission groups. */ + PERM_GROUPS("authme.debug.group"), + + /** Permission to view data from the database. */ + PLAYER_AUTH_VIEWER("authme.debug.db"), + + /** Permission to view spawn information. */ + SPAWN_LOCATION("authme.debug.spawn"), + + /** Permission to use the test email sender. */ + TEST_EMAIL("authme.debug.mail"); + + private final String node; + + /** + * Constructor. + * + * @param node the permission node + */ + DebugSectionPermissions(String node) { + this.node = node; + } + + @Override + public String getNode() { + return node; + } + + @Override + public DefaultPermission getDefaultPermission() { + return DefaultPermission.OP_ONLY; + } +} diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java index 2160eea5..aaeb0eea 100644 --- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java +++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java @@ -29,12 +29,7 @@ public enum PlayerStatePermission implements PermissionNode { /** * Permission to bypass the purging process. */ - BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED), - - /** - * Permission to use the /authme debug command. - */ - DEBUG_COMMAND("authme.debug", DefaultPermission.OP_ONLY); + BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED); /** * The permission node. diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java index 946af52b..3578c80f 100644 --- a/src/main/java/fr/xephi/authme/security/HashUtils.java +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -85,12 +85,22 @@ public final class HashUtils { * @param algorithm The algorithm to hash the message with * @return The digest in its hexadecimal representation */ - private static String hash(String message, MessageDigestAlgorithm algorithm) { - MessageDigest md = getDigest(algorithm); - md.reset(); - md.update(message.getBytes()); - byte[] digest = md.digest(); + public static String hash(String message, MessageDigest algorithm) { + algorithm.reset(); + algorithm.update(message.getBytes()); + byte[] digest = algorithm.digest(); return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); } + /** + * Hash the message with the given algorithm and return the hash in its hexadecimal notation. + * + * @param message The message to hash + * @param algorithm The algorithm to hash the message with + * @return The digest in its hexadecimal representation + */ + private static String hash(String message, MessageDigestAlgorithm algorithm) { + return hash(message, getDigest(algorithm)); + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java index 989ef838..9f557438 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java +++ b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java @@ -1,14 +1,17 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.MessageDigestAlgorithm; + +import java.security.MessageDigest; public class RoyalAuth extends UnsaltedMethod { @Override public String computeHash(String password) { - for (int i = 0; i < 25; i++) { - // TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time... - password = HashUtils.sha512(password); + MessageDigest algorithm = HashUtils.getDigest(MessageDigestAlgorithm.SHA512); + for (int i = 0; i < 25; ++i) { + password = HashUtils.hash(password, algorithm); } return password; } diff --git a/src/main/java/fr/xephi/authme/service/BackupService.java b/src/main/java/fr/xephi/authme/service/BackupService.java index 846e3441..0141e8f6 100644 --- a/src/main/java/fr/xephi/authme/service/BackupService.java +++ b/src/main/java/fr/xephi/authme/service/BackupService.java @@ -1,12 +1,15 @@ package fr.xephi.authme.service; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.datasource.DataSourceType; +import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.BackupSettings; import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.util.FileUtils; +import org.bukkit.command.CommandSender; +import javax.inject.Inject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -16,80 +19,80 @@ import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import static fr.xephi.authme.util.Utils.logAndSendMessage; +import static fr.xephi.authme.util.Utils.logAndSendWarning; + /** - * The backup management class - * - * @author stefano + * Performs a backup of the data source. */ public class BackupService { - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); - - private final String dbName; - private final String dbUserName; - private final String dbPassword; - private final String tblname; - private final String path; + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); private final File dataFolder; + private final File backupFolder; private final Settings settings; /** - * Constructor for PerformBackup. + * Constructor. * - * @param instance AuthMe - * @param settings The plugin settings + * @param dataFolder the data folder + * @param settings the plugin settings */ - public BackupService(AuthMe instance, Settings settings) { - this.dataFolder = instance.getDataFolder(); - this.settings = settings; - this.dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); - this.dbUserName = settings.getProperty(DatabaseSettings.MYSQL_USERNAME); - this.dbPassword = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD); - this.tblname = settings.getProperty(DatabaseSettings.MYSQL_TABLE); - - String dateString = DATE_FORMAT.format(new Date()); - this.path = String.join(File.separator, - instance.getDataFolder().getPath(), "backups", "backup" + dateString); + @Inject + public BackupService(@DataFolder File dataFolder, Settings settings) { + this.dataFolder = dataFolder; + this.backupFolder = new File(dataFolder, "backups"); + this.settings = settings; } /** - * Perform a backup with the given reason. + * Performs a backup for the given reason. * - * @param cause The cause of the backup. + * @param cause backup reason */ public void doBackup(BackupCause cause) { + doBackup(cause, null); + } + + /** + * Performs a backup for the given reason. + * + * @param cause backup reason + * @param sender the command sender (nullable) + */ + public void doBackup(BackupCause cause, CommandSender sender) { if (!settings.getProperty(BackupSettings.ENABLED)) { // Print a warning if the backup was requested via command or by another plugin if (cause == BackupCause.COMMAND || cause == BackupCause.OTHER) { - ConsoleLogger.warning("Can't perform a Backup: disabled in configuration. Cause of the Backup: " - + cause.name()); + logAndSendWarning(sender, + "Can't perform a backup: disabled in configuration. Cause of the backup: " + cause.name()); } return; - } - - // Check whether a backup should be made at the specified point in time - if (BackupCause.START.equals(cause) && !settings.getProperty(BackupSettings.ON_SERVER_START) - || BackupCause.STOP.equals(cause) && !settings.getProperty(BackupSettings.ON_SERVER_STOP)) { + } else if (BackupCause.START == cause && !settings.getProperty(BackupSettings.ON_SERVER_START) + || BackupCause.STOP == cause && !settings.getProperty(BackupSettings.ON_SERVER_STOP)) { + // Don't perform backup on start or stop if so configured return; } // Do backup and check return value! if (doBackup()) { - ConsoleLogger.info("A backup has been performed successfully. Cause of the Backup: " + cause.name()); + logAndSendMessage(sender, + "A backup has been performed successfully. Cause of the backup: " + cause.name()); } else { - ConsoleLogger.warning("Error while performing a backup! Cause of the Backup: " + cause.name()); + logAndSendWarning(sender, "Error while performing a backup! Cause of the backup: " + cause.name()); } } - public boolean doBackup() { + private boolean doBackup() { DataSourceType dataSourceType = settings.getProperty(DatabaseSettings.BACKEND); switch (dataSourceType) { case FILE: - return fileBackup("auths.db"); + return performFileBackup("auths.db"); case MYSQL: - return mySqlBackup(); + return performMySqlBackup(); case SQLITE: - return fileBackup(dbName + ".db"); + String dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + return performFileBackup(dbName + ".db"); default: ConsoleLogger.warning("Unknown data source type '" + dataSourceType + "' for backup"); } @@ -97,17 +100,15 @@ public class BackupService { return false; } - private boolean mySqlBackup() { - File dirBackup = new File(dataFolder + File.separator + "backups"); + private boolean performMySqlBackup() { + FileUtils.createDirectory(backupFolder); + File sqlBackupFile = constructBackupFile("sql"); - if (!dirBackup.exists()) { - dirBackup.mkdir(); - } String backupWindowsPath = settings.getProperty(BackupSettings.MYSQL_WINDOWS_PATH); - boolean isUsingWindows = checkWindows(backupWindowsPath); + boolean isUsingWindows = useWindowsCommand(backupWindowsPath); String backupCommand = isUsingWindows - ? backupWindowsPath + "\\bin\\mysqldump.exe" + buildMysqlDumpArguments() - : "mysqldump" + buildMysqlDumpArguments(); + ? backupWindowsPath + "\\bin\\mysqldump.exe" + buildMysqlDumpArguments(sqlBackupFile) + : "mysqldump" + buildMysqlDumpArguments(sqlBackupFile); try { Process runtimeProcess = Runtime.getRuntime().exec(backupCommand); @@ -124,14 +125,12 @@ public class BackupService { return false; } - private boolean fileBackup(String backend) { - File dirBackup = new File(dataFolder + File.separator + "backups"); - - if (!dirBackup.exists()) - dirBackup.mkdir(); + private boolean performFileBackup(String filename) { + FileUtils.createDirectory(backupFolder); + File backupFile = constructBackupFile("db"); try { - copy("plugins" + File.separator + "AuthMe" + File.separator + backend, path + ".db"); + copy(new File(dataFolder, filename), backupFile); return true; } catch (IOException ex) { ConsoleLogger.logException("Encountered an error during file backup:", ex); @@ -146,7 +145,7 @@ public class BackupService { * @param windowsPath The path to check * @return True if the path is correct, false if it is incorrect or the OS is not Windows */ - private static boolean checkWindows(String windowsPath) { + private static boolean useWindowsCommand(String windowsPath) { String isWin = System.getProperty("os.name").toLowerCase(); if (isWin.contains("win")) { if (new File(windowsPath + "\\bin\\mysqldump.exe").exists()) { @@ -162,28 +161,42 @@ public class BackupService { /** * Builds the command line arguments to pass along when running the {@code mysqldump} command. * + * @param sqlBackupFile the file to back up to * @return the mysqldump command line arguments */ - private String buildMysqlDumpArguments() { - return " -u " + dbUserName + " -p" + dbPassword + " " + dbName - + " --tables " + tblname + " -r " + path + ".sql"; + private String buildMysqlDumpArguments(File sqlBackupFile) { + String dbUsername = settings.getProperty(DatabaseSettings.MYSQL_USERNAME); + String dbPassword = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD); + String dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + + return " -u " + dbUsername + " -p" + dbPassword + " " + dbName + + " --tables " + tableName + " -r " + sqlBackupFile.getPath() + ".sql"; } - private static void copy(String src, String dst) throws IOException { - InputStream in = new FileInputStream(src); - OutputStream out = new FileOutputStream(dst); + /** + * Constructs the file name to back up the data source to. + * + * @param fileExtension the file extension to use (e.g. sql) + * @return the file to back up the data to + */ + private File constructBackupFile(String fileExtension) { + String dateString = dateFormat.format(new Date()); + return new File(backupFolder, "backup" + dateString + "." + fileExtension); + } - // Transfer bytes from in to out - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); + private static void copy(File src, File dst) throws IOException { + try (InputStream in = new FileInputStream(src); + OutputStream out = new FileOutputStream(dst)) { + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } } - in.close(); - out.close(); } - /** * Possible backup causes. */ diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java index 26f7d03b..13f28004 100644 --- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java +++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java @@ -45,7 +45,6 @@ public class SpawnLoader implements Reloadable { */ @Inject SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) { - // TODO ljacqu 20160312: Check if resource could be copied and handle the case if not File spawnFile = new File(pluginFolder, "spawn.yml"); FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); this.authMeConfigurationFile = spawnFile; diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java index 57bb5941..190c5b7d 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java @@ -8,19 +8,19 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty; public final class BackupSettings implements SettingsHolder { - @Comment("Enable or disable automatic backup") + @Comment("General configuration for backups: if false, no backups are possible") public static final Property ENABLED = newProperty("BackupSystem.ActivateBackup", false); - @Comment("Set backup at every start of server") + @Comment("Create backup at every start of server") public static final Property ON_SERVER_START = newProperty("BackupSystem.OnServerStart", false); - @Comment("Set backup at every stop of server") + @Comment("Create backup at every stop of server") public static final Property ON_SERVER_STOP = newProperty("BackupSystem.OnServerStop", true); - @Comment("Windows only mysql installation Path") + @Comment("Windows only: MySQL installation path") public static final Property MYSQL_WINDOWS_PATH = newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\"); 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 985630cf..0fddecd5 100644 --- a/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java +++ b/src/main/java/fr/xephi/authme/task/purge/PurgeExecutor.java @@ -194,7 +194,6 @@ class PurgeExecutor { } // TODO: What is this method for? Is it correct? - // TODO: Make it work with OfflinePlayers group data. synchronized void purgePermissions(Collection cleared) { if (!settings.getProperty(PurgeSettings.REMOVE_PERMISSIONS)) { return; diff --git a/src/main/java/fr/xephi/authme/util/FileUtils.java b/src/main/java/fr/xephi/authme/util/FileUtils.java index c9edcf73..fa8c858a 100644 --- a/src/main/java/fr/xephi/authme/util/FileUtils.java +++ b/src/main/java/fr/xephi/authme/util/FileUtils.java @@ -30,7 +30,7 @@ public final class FileUtils { public static boolean copyFileFromResource(File destinationFile, String resourcePath) { if (destinationFile.exists()) { return true; - } else if (!destinationFile.getParentFile().exists() && !destinationFile.getParentFile().mkdirs()) { + } else if (!createDirectory(destinationFile.getParentFile())) { ConsoleLogger.warning("Cannot create parent directories for '" + destinationFile + "'"); return false; } @@ -50,6 +50,20 @@ public final class FileUtils { return false; } + /** + * Creates the given directory. + * + * @param dir the directory to create + * @return true upon success, false otherwise + */ + public static boolean createDirectory(File dir) { + if (!dir.exists() && !dir.mkdirs()) { + ConsoleLogger.warning("Could not create directory '" + dir + "'"); + return false; + } + return true; + } + /** * Returns a JAR file as stream. Returns null if it doesn't exist. * diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 99520c35..ad4ccb9e 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -1,6 +1,7 @@ package fr.xephi.authme.util; import fr.xephi.authme.ConsoleLogger; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -69,6 +70,22 @@ public final class Utils { } } + /** + * Sends a warning to the given sender (null safe), and logs the warning to the console. + * This method is aware that the command sender might be the console sender and avoids + * displaying the message twice in this case. + * + * @param sender the sender to inform + * @param message the warning to log and send + */ + public static void logAndSendWarning(CommandSender sender, String message) { + ConsoleLogger.warning(message); + // Make sure sender is not console user, which will see the message from ConsoleLogger already + if (sender != null && !(sender instanceof ConsoleCommandSender)) { + sender.sendMessage(ChatColor.RED + message); + } + } + /** * Null-safe way to check whether a collection is empty or not. * diff --git a/src/main/resources/messages/messages_br.yml b/src/main/resources/messages/messages_br.yml index 7adea716..c617d8b9 100644 --- a/src/main/resources/messages/messages_br.yml +++ b/src/main/resources/messages/messages_br.yml @@ -49,9 +49,9 @@ two_factor_create: '&2O seu código secreto é %code. Você pode verificá-lo a recovery_code_sent: 'Um código de recuperação para redefinir sua senha foi enviada para o seu e-mail.' # TODO: Missing tags %count recovery_code_incorrect: 'O código de recuperação esta incorreto! Use /email recovery [email] para gerar um novo!' -# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' -# TODO recovery_code_correct: 'Recovery code entered correctly!' -# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' +recovery_tries_exceeded: 'Você excedeu o limite de tentativas de usar o código de recuperação! Use "/email recovery [email]" para gerar um novo.' +recovery_code_correct: 'Código de recuperação aceito!' +recovery_change_password: 'Por favor, use o comando /email setpassword para alterar sua senha imediatamente!' vb_nonActiv: '&cA sua conta ainda não está ativada, por favor, verifique seus e-mails!' usage_unreg: '&cUse: /unregister ' pwd_changed: '&2Senha alterada com sucesso!' @@ -92,8 +92,8 @@ email_send_failure: '&cO e-mail não pôde ser enviado, reporte isso a um admini show_no_email: '&2Você atualmente não têm endereço de e-mail associado a esta conta.' add_email: '&3Por favor, adicione seu e-mail para a sua conta com o comando "/email add "' recovery_email: '&3Esqueceu sua senha? Por favor, use o comando "/email recovery "' -# TODO change_password_expired: 'You cannot change your password using this command anymore.' -# TODO email_cooldown_error: '&cAn email was already sent recently. You must wait %time before you can send a new one.' +change_password_expired: 'Você não pode mais usar esse comando de recuperação de senha!' +email_cooldown_error: '&cUm e-mail já foi enviado, espere mais %time antes de enviar novamente!' # Captcha usage_captcha: '&3Para iniciar sessão você tem que resolver um código captcha, utilize o comando "/captcha "' @@ -101,11 +101,11 @@ wrong_captcha: '&cCaptcha errado, por favor, escreva "/captcha THE_CAPTCHA" no c valid_captcha: '&2Código Captcha resolvido corretamente!' # Time units -# TODO second: 'second' -# TODO seconds: 'seconds' -# TODO minute: 'minute' -# TODO minutes: 'minutes' -# TODO hour: 'hour' -# TODO hours: 'hours' -# TODO day: 'day' -# TODO days: 'days' +second: 'segundo' +seconds: 'segundos' +minute: 'minuto' +minutes: 'minutos' +hour: 'hora' +hours: 'horas' +day: 'dia' +days: 'dias' diff --git a/src/main/resources/messages/messages_es.yml b/src/main/resources/messages/messages_es.yml index 71018829..c5f2fe07 100644 --- a/src/main/resources/messages/messages_es.yml +++ b/src/main/resources/messages/messages_es.yml @@ -48,9 +48,9 @@ two_factor_create: '&2Tu código secreto es %code. Lo puedes escanear desde aqu recovery_code_sent: 'El código de recuperación para recuperar tu contraseña se ha enviado a tu correo.' # TODO: Missing tags %count recovery_code_incorrect: '¡El código de recuperación no es correcto! Usa "/email recovery [email]" para generar uno nuevo' -# TODO recovery_tries_exceeded: 'You have exceeded the maximum number attempts to enter the recovery code. Use "/email recovery [email]" to generate a new one.' -# TODO recovery_code_correct: 'Recovery code entered correctly!' -# TODO recovery_change_password: 'Please use the command /email setpassword to change your password immediately.' +recovery_tries_exceeded: 'Has excedido el número máximo de intentos para introducir el código de recuperación. Escribe "/email recovery [tuEmail]" para generar uno nuevo.' +recovery_code_correct: '¡Código de recuperación introducido correctamente!' +recovery_change_password: 'Por favor usa el comando "/email setpassword " para cambiar tu contraseña inmediatamente.' vb_nonActiv: '&fTu cuenta no está activada aún, ¡revisa tu correo!' usage_unreg: '&cUso: /unregister contraseña' pwd_changed: '&c¡Contraseña cambiada!' @@ -91,7 +91,7 @@ email_send_failure: 'No se ha podido enviar el correo electrónico. Por favor, c show_no_email: '&2No tienes ningun E-Mail asociado en esta cuenta.' add_email: '&cPor favor agrega tu e-mail con: /email add tuEmail confirmarEmail' recovery_email: '&c¿Olvidaste tu contraseña? Por favor usa /email recovery ' -# TODO change_password_expired: 'You cannot change your password using this command anymore.' +change_password_expired: 'No puedes cambiar la contraseña utilizando este comando.' email_cooldown_error: '&cEl correo ha sido enviado recientemente. Debes esperar %time antes de volver a enviar uno nuevo.' # Captcha diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f04829d4..8629a453 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,7 +17,7 @@ softdepend: commands: authme: description: AuthMe op commands - usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug + usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug login: description: Login command usage: /login @@ -45,7 +45,7 @@ commands: - cp email: description: Add email or recover password - usage: /email show|add|change|recover + usage: /email show|add|change|recover|code|setpassword captcha: description: Captcha Command usage: /captcha @@ -55,6 +55,7 @@ permissions: children: authme.admin.accounts: true authme.admin.antibotmessages: true + authme.admin.backup: true authme.admin.changemail: true authme.admin.changepassword: true authme.admin.converter: true @@ -81,6 +82,9 @@ permissions: authme.admin.antibotmessages: description: Permission to see Antibot messages. default: op + authme.admin.backup: + description: Allows to use the backup command. + default: op authme.admin.changemail: description: Administrator command to set or change the email address of a user. default: op @@ -154,7 +158,47 @@ permissions: description: Permission to bypass the purging process. default: false authme.debug: - description: Permission to use the /authme debug command. + description: Gives access to /authme debug and all its sections + children: + authme.debug.command: true + authme.debug.country: true + authme.debug.db: true + authme.debug.group: true + authme.debug.limbo: true + authme.debug.mail: true + authme.debug.perm: true + authme.debug.spawn: true + authme.debug.stats: true + authme.debug.valid: true + authme.debug.command: + description: General permission to use the /authme debug command. + default: op + authme.debug.country: + description: Permission to use the country lookup section. + default: op + authme.debug.db: + description: Permission to view data from the database. + default: op + authme.debug.group: + description: Permission to view permission groups. + default: op + authme.debug.limbo: + description: Permission to use the limbo data viewer. + default: op + authme.debug.mail: + description: Permission to use the test email sender. + default: op + authme.debug.perm: + description: Permission to use the permission checker. + default: op + authme.debug.spawn: + description: Permission to view spawn information. + default: op + authme.debug.stats: + description: Permission to use the stats section. + default: op + authme.debug.valid: + description: Permission to use sample validation. default: op authme.player.*: description: Gives access to all player commands diff --git a/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java b/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java new file mode 100644 index 00000000..a6033ffd --- /dev/null +++ b/src/test/java/fr/xephi/authme/IsEqualByReflectionMatcher.java @@ -0,0 +1,84 @@ +package fr.xephi.authme; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.junit.Assert.fail; + +/** + * Matcher which checks with reflection that all fields have the same value. + * This matcher considers all non-static fields until the Object parent. + */ +public final class IsEqualByReflectionMatcher extends TypeSafeMatcher { + + private final T expected; + + private IsEqualByReflectionMatcher(T expected) { + this.expected = expected; + } + + /** + * Creates a matcher that checks if all fields are the same as on the {@code expected} object. + * + * @param expected the object to match + * @param the object's type + * @return the matcher for the expected object + */ + public static Matcher isEqualTo(T expected) { + return new IsEqualByReflectionMatcher<>(expected); + } + + @Override + protected boolean matchesSafely(T item) { + return assertAreFieldsEqual(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("parameters " + expected); + } + + private boolean assertAreFieldsEqual(T item) { + if (expected.getClass() != item.getClass()) { + fail("Classes don't match, got " + expected.getClass().getSimpleName() + + " and " + item.getClass().getSimpleName()); + return false; + } + + List fieldsToCheck = getAllFields(expected); + for (Field field : fieldsToCheck) { + Object lhsValue = ReflectionTestUtils.getFieldValue(field, expected); + Object rhsValue = ReflectionTestUtils.getFieldValue(field, item); + if (!Objects.equals(lhsValue, rhsValue)) { + fail("Field '" + field.getName() + "' does not have same value: '" + + lhsValue + "' vs. '" + rhsValue + "'"); + return false; + } + } + return true; + } + + private static List getAllFields(Object object) { + List fields = new ArrayList<>(); + Class currentClass = object.getClass(); + while (currentClass != null) { + for (Field f : currentClass.getDeclaredFields()) { + if (!Modifier.isStatic(f.getModifiers())) { + fields.add(f); + } + } + if (currentClass == Object.class) { + break; + } + currentClass = currentClass.getSuperclass(); + } + return fields; + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java new file mode 100644 index 00000000..a8703444 --- /dev/null +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java @@ -0,0 +1,154 @@ +package fr.xephi.authme.command.executable.authme.debug; + +import fr.xephi.authme.initialization.factory.Factory; +import fr.xephi.authme.permission.DebugSectionPermissions; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; +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.Arrays; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; + +/** + * Test for {@link DebugCommand}. + */ +@RunWith(MockitoJUnitRunner.class) +public class DebugCommandTest { + + /** + * Number we test against if we expect an action to have been performed for each debug section. + * This is a minimum number so tests don't fail each time a new debug section is added; however, + * it should be close to the total. + */ + private static final int MIN_DEBUG_SECTIONS = 9; + + @InjectMocks + private DebugCommand command; + + @Mock + private Factory debugSectionFactory; + + @Mock + private PermissionsManager permissionsManager; + + @Before + @SuppressWarnings("unchecked") + public void initFactory() { + given(debugSectionFactory.newInstance(any(Class.class))).willAnswer( + invocation -> { + Class classArgument = invocation.getArgument(0); + checkArgument(DebugSection.class.isAssignableFrom(classArgument)); + return spy(classArgument); + }); + } + + @Test + public void shouldListAllAvailableDebugSections() { + // given + CommandSender sender = mock(CommandSender.class); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(false); + given(permissionsManager.hasPermission(sender, DebugSectionPermissions.INPUT_VALIDATOR)).willReturn(true); + given(permissionsManager.hasPermission(sender, DebugSectionPermissions.DATA_STATISTICS)).willReturn(true); + + // when + command.executeCommand(sender, emptyList()); + + // then + verify(debugSectionFactory, atLeast(MIN_DEBUG_SECTIONS)).newInstance(any(Class.class)); + verify(permissionsManager, atLeast(MIN_DEBUG_SECTIONS)).hasPermission(eq(sender), any(DebugSectionPermissions.class)); + + ArgumentCaptor strCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, times(3)).sendMessage(strCaptor.capture()); + assertThat(strCaptor.getAllValues(), contains( + containsString("Sections available to you"), + containsString("stats: Outputs general data statistics"), + containsString("valid: Check if email / password is valid"))); + } + + @Test + public void shouldNotListAnyDebugSection() { + // given + CommandSender sender = mock(CommandSender.class); + given(permissionsManager.hasPermission(eq(sender), any(PermissionNode.class))).willReturn(false); + + // when + command.executeCommand(sender, emptyList()); + + // then + verify(debugSectionFactory, atLeast(MIN_DEBUG_SECTIONS)).newInstance(any(Class.class)); + verify(permissionsManager, atLeast(MIN_DEBUG_SECTIONS)).hasPermission(eq(sender), any(DebugSectionPermissions.class)); + + ArgumentCaptor strCaptor = ArgumentCaptor.forClass(String.class); + verify(sender, times(2)).sendMessage(strCaptor.capture()); + assertThat(strCaptor.getAllValues(), contains( + equalTo("Sections available to you:"), + containsString("You don't have permission to view any debug section"))); + } + + @Test + public void shouldRunSection() { + // given + DebugSection section = spy(InputValidator.class); + doNothing().when(section).execute(any(CommandSender.class), anyList()); + // Mockito throws a runtime error if below we use the usual "given(factory.newInstance(...)).willReturn(...)" + doReturn(section).when(debugSectionFactory).newInstance(InputValidator.class); + + CommandSender sender = mock(CommandSender.class); + given(permissionsManager.hasPermission(sender, section.getRequiredPermission())).willReturn(true); + List arguments = Arrays.asList(section.getName().toUpperCase(), "test", "toast"); + + // when + command.executeCommand(sender, arguments); + + // then + verify(permissionsManager).hasPermission(sender, section.getRequiredPermission()); + verify(section).execute(sender, Arrays.asList("test", "toast")); + } + + @Test + public void shouldNotRunSectionForMissingPermission() { + // given + DebugSection section = spy(InputValidator.class); + // Mockito throws a runtime error if below we use the usual "given(factory.newInstance(...)).willReturn(...)" + doReturn(section).when(debugSectionFactory).newInstance(InputValidator.class); + + CommandSender sender = mock(CommandSender.class); + given(permissionsManager.hasPermission(sender, section.getRequiredPermission())).willReturn(false); + List arguments = Arrays.asList(section.getName().toUpperCase(), "test"); + + // when + command.executeCommand(sender, arguments); + + // then + verify(permissionsManager).hasPermission(sender, section.getRequiredPermission()); + verify(section, never()).execute(any(CommandSender.class), anyList()); + verify(sender).sendMessage(argThat(containsString("You don't have permission"))); + } +} diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java index 9f7c6a92..4bd8eab1 100644 --- a/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionCheckerTest.java @@ -20,9 +20,9 @@ import java.util.stream.Collectors; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeast; @@ -55,8 +55,7 @@ public class HasPermissionCheckerTest { .collect(Collectors.toList()); // when / then - assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES.containsAll(permissionClasses), equalTo(true)); - assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES, hasSize(permissionClasses.size())); + assertThat(HasPermissionChecker.PERMISSION_NODE_CLASSES, containsInAnyOrder(permissionClasses.toArray())); } @Test diff --git a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java index 6224a606..d345ca8e 100644 --- a/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java +++ b/src/test/java/fr/xephi/authme/command/executable/register/RegisterCommandTest.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.register; -import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.TestHelper; import fr.xephi.authme.mail.EmailService; import fr.xephi.authme.message.MessageKey; @@ -10,7 +9,6 @@ import fr.xephi.authme.process.register.RegistrationType; import fr.xephi.authme.process.register.executors.EmailRegisterParams; import fr.xephi.authme.process.register.executors.PasswordRegisterParams; import fr.xephi.authme.process.register.executors.RegistrationMethod; -import fr.xephi.authme.process.register.executors.RegistrationParameters; import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.service.CommonService; @@ -20,9 +18,6 @@ import fr.xephi.authme.settings.properties.SecuritySettings; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -31,16 +26,11 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.List; -import java.util.Objects; +import static fr.xephi.authme.IsEqualByReflectionMatcher.isEqualTo; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -306,56 +296,4 @@ public class RegisterCommandTest { verify(management).performRegister(eq(RegistrationMethod.PASSWORD_REGISTRATION), argThat(isEqualTo(PasswordRegisterParams.of(player, "myPass", null)))); } - - - // TODO ljacqu 20170317: Document and extract as util - - private static

Matcher

isEqualTo(P expected) { - return new TypeSafeMatcher

() { - @Override - protected boolean matchesSafely(RegistrationParameters item) { - assertAreParamsEqual(expected, item); - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("parameters " + expected); - } - }; - } - - private static void assertAreParamsEqual(RegistrationParameters lhs, RegistrationParameters rhs) { - if (lhs.getClass() != rhs.getClass()) { - fail("Params classes don't match, got " + lhs.getClass().getSimpleName() - + " and " + rhs.getClass().getSimpleName()); - } - - List fieldsToCheck = getFields(lhs); - for (Field field : fieldsToCheck) { - Object lhsValue = ReflectionTestUtils.getFieldValue(field, lhs); - Object rhsValue = ReflectionTestUtils.getFieldValue(field, rhs); - if (!Objects.equals(lhsValue, rhsValue)) { - fail("Field '" + field.getName() + "' does not have same value: '" - + lhsValue + "' vs. '" + rhsValue + "'"); - } - } - } - - private static List getFields(RegistrationParameters params) { - List fields = new ArrayList<>(); - Class currentClass = params.getClass(); - while (currentClass != null) { - for (Field f : currentClass.getDeclaredFields()) { - if (!Modifier.isStatic(f.getModifiers())) { - fields.add(f); - } - } - if (currentClass == RegistrationParameters.class) { - break; - } - currentClass = currentClass.getSuperclass(); - } - return fields; - } } diff --git a/src/test/java/fr/xephi/authme/permission/AbstractPermissionsEnumTest.java b/src/test/java/fr/xephi/authme/permission/AbstractPermissionsEnumTest.java new file mode 100644 index 00000000..4b327ec4 --- /dev/null +++ b/src/test/java/fr/xephi/authme/permission/AbstractPermissionsEnumTest.java @@ -0,0 +1,52 @@ +package fr.xephi.authme.permission; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.fail; + +/** + * Has common tests for enums implementing {@link PermissionNode}. + */ +public abstract class AbstractPermissionsEnumTest { + + @Test + public void shouldAllStartWitRequiredPrefix() { + // given + String requiredPrefix = getRequiredPrefix(); + + // when/then + for (PermissionNode permission : getPermissionNodes()) { + if (!permission.getNode().startsWith(requiredPrefix)) { + fail("The permission '" + permission + "' does not start with the required prefix '" + + requiredPrefix + "'"); + } + } + } + + @Test + public void shouldHaveUniqueNodes() { + // given + Set nodes = new HashSet<>(); + + // when/then + for (PermissionNode permission : getPermissionNodes()) { + if (!nodes.add(permission.getNode())) { + fail("More than one enum value defines the node '" + permission.getNode() + "'"); + } + } + } + + /** + * @return the permission nodes to test + */ + protected abstract PermissionNode[] getPermissionNodes(); + + /** + * @return text with which all permission nodes must start with + */ + protected abstract String getRequiredPrefix(); + +} diff --git a/src/test/java/fr/xephi/authme/permission/AdminPermissionTest.java b/src/test/java/fr/xephi/authme/permission/AdminPermissionTest.java index 3cfa0e27..f3e4f1fd 100644 --- a/src/test/java/fr/xephi/authme/permission/AdminPermissionTest.java +++ b/src/test/java/fr/xephi/authme/permission/AdminPermissionTest.java @@ -1,42 +1,18 @@ package fr.xephi.authme.permission; -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -import static org.junit.Assert.fail; - /** * Test for {@link AdminPermission}. */ -public class AdminPermissionTest { +public class AdminPermissionTest extends AbstractPermissionsEnumTest { - @Test - public void shouldStartWithAuthMeAdminPrefix() { - // given - String requiredPrefix = "authme.admin."; - - // when/then - for (AdminPermission permission : AdminPermission.values()) { - if (!permission.getNode().startsWith(requiredPrefix)) { - fail("The permission '" + permission + "' does not start with the required prefix '" - + requiredPrefix + "'"); - } - } + @Override + protected PermissionNode[] getPermissionNodes() { + return AdminPermission.values(); } - @Test - public void shouldHaveUniqueNodes() { - // given - Set nodes = new HashSet<>(); - - // when/then - for (AdminPermission permission : AdminPermission.values()) { - if (!nodes.add(permission.getNode())) { - fail("More than one enum value defines the node '" + permission.getNode() + "'"); - } - } + @Override + protected String getRequiredPrefix() { + return "authme.admin."; } } diff --git a/src/test/java/fr/xephi/authme/permission/DebugSectionPermissionsTest.java b/src/test/java/fr/xephi/authme/permission/DebugSectionPermissionsTest.java new file mode 100644 index 00000000..3e0a9f01 --- /dev/null +++ b/src/test/java/fr/xephi/authme/permission/DebugSectionPermissionsTest.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.permission; + +/** + * Test for {@link DebugSectionPermissions}. + */ +public class DebugSectionPermissionsTest extends AbstractPermissionsEnumTest { + + @Override + protected PermissionNode[] getPermissionNodes() { + return DebugSectionPermissions.values(); + } + + @Override + protected String getRequiredPrefix() { + return "authme.debug."; + } +} diff --git a/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java b/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java index 757319d5..6d7a37e2 100644 --- a/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/permission/PermissionConsistencyTest.java @@ -31,7 +31,7 @@ public class PermissionConsistencyTest { /** Wildcard permissions (present in plugin.yml but not in the codebase). */ private static final Set PLUGIN_YML_PERMISSIONS_WILDCARDS = - ImmutableSet.of("authme.admin.*", "authme.player.*", "authme.player.email"); + ImmutableSet.of("authme.admin.*", "authme.player.*", "authme.player.email", "authme.debug"); /** Name of the fields that make up a permission entry in plugin.yml. */ private static final Set PERMISSION_FIELDS = ImmutableSet.of("description", "default", "children"); diff --git a/src/test/java/fr/xephi/authme/permission/PlayerPermissionTest.java b/src/test/java/fr/xephi/authme/permission/PlayerPermissionTest.java index ad8f6fe9..32a45b98 100644 --- a/src/test/java/fr/xephi/authme/permission/PlayerPermissionTest.java +++ b/src/test/java/fr/xephi/authme/permission/PlayerPermissionTest.java @@ -1,41 +1,17 @@ package fr.xephi.authme.permission; -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -import static org.junit.Assert.fail; - /** * Test for {@link PlayerPermission}. */ -public class PlayerPermissionTest { +public class PlayerPermissionTest extends AbstractPermissionsEnumTest { - @Test - public void shouldStartWithPlayerPrefix() { - // given - String playerBranch = "authme.player."; - - // when/then - for (PlayerPermission permission : PlayerPermission.values()) { - if (!permission.getNode().startsWith(playerBranch)) { - fail("The permission '" + permission + "' should use a node with the player-specific branch '" - + playerBranch + "'"); - } - } + @Override + protected PermissionNode[] getPermissionNodes() { + return PlayerPermission.values(); } - @Test - public void shouldHaveUniqueNodes() { - // given - Set nodes = new HashSet<>(); - - // when/then - for (PlayerPermission permission : PlayerPermission.values()) { - if (!nodes.add(permission.getNode())) { - fail("More than one enum value defines the node '" + permission.getNode() + "'"); - } - } + @Override + protected String getRequiredPrefix() { + return "authme.player."; } } diff --git a/src/test/java/fr/xephi/authme/permission/PlayerStatePermissionTest.java b/src/test/java/fr/xephi/authme/permission/PlayerStatePermissionTest.java index 6c3e3d16..56e5e1ce 100644 --- a/src/test/java/fr/xephi/authme/permission/PlayerStatePermissionTest.java +++ b/src/test/java/fr/xephi/authme/permission/PlayerStatePermissionTest.java @@ -2,7 +2,7 @@ package fr.xephi.authme.permission; import org.junit.Test; -import java.util.HashSet; +import java.util.Collection; import java.util.Set; import static com.google.common.collect.Sets.newHashSet; @@ -11,39 +11,32 @@ import static org.junit.Assert.fail; /** * Test for {@link PlayerStatePermission}. */ -public class PlayerStatePermissionTest { +public class PlayerStatePermissionTest extends AbstractPermissionsEnumTest { @Test - public void shouldStartWithAuthMeAdminPrefix() { + public void shouldNotStartWithOtherPrefixes() { // given - String requiredPrefix = "authme."; - Set forbiddenPrefixes = newHashSet("authme.player", "authme.admin"); + Set forbiddenPrefixes = newHashSet("authme.player", "authme.admin", "authme.debug"); // when/then for (PlayerStatePermission permission : PlayerStatePermission.values()) { - if (!permission.getNode().startsWith(requiredPrefix)) { - fail("The permission '" + permission + "' does not start with the required prefix '" - + requiredPrefix + "'"); - } else if (hasAnyPrefix(permission.getNode(), forbiddenPrefixes)) { + if (startsWithAny(permission.getNode(), forbiddenPrefixes)) { fail("The permission '" + permission + "' should not start with any of " + forbiddenPrefixes); } } } - @Test - public void shouldHaveUniqueNodes() { - // given - Set nodes = new HashSet<>(); - - // when/then - for (PlayerStatePermission permission : PlayerStatePermission.values()) { - if (!nodes.add(permission.getNode())) { - fail("More than one enum value defines the node '" + permission.getNode() + "'"); - } - } + @Override + protected PermissionNode[] getPermissionNodes() { + return PlayerStatePermission.values(); } - private static boolean hasAnyPrefix(String node, Set prefixes) { + @Override + protected String getRequiredPrefix() { + return "authme."; + } + + private static boolean startsWithAny(String node, Collection prefixes) { for (String prefix : prefixes) { if (node.startsWith(prefix)) { return true; @@ -51,5 +44,4 @@ public class PlayerStatePermissionTest { } return false; } - } diff --git a/src/test/java/fr/xephi/authme/service/AntiBotServiceTest.java b/src/test/java/fr/xephi/authme/service/AntiBotServiceTest.java index dd6b6c78..f1bb641d 100644 --- a/src/test/java/fr/xephi/authme/service/AntiBotServiceTest.java +++ b/src/test/java/fr/xephi/authme/service/AntiBotServiceTest.java @@ -11,7 +11,6 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.ProtectionSettings; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -82,7 +81,6 @@ public class AntiBotServiceTest { } @Test - @Ignore // TODO ljacqu 20161030: Fix test public void shouldActivateAntibot() { // given - listening antibot runSyncDelayedTaskWithDelay(bukkitService); diff --git a/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java b/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java index e3635512..f177cbc6 100644 --- a/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java +++ b/src/test/java/tools/docs/permissions/PermissionNodesGatherer.java @@ -25,7 +25,7 @@ public class PermissionNodesGatherer { * the second group should contain the enum value. */ private static final Pattern JAVADOC_WITH_ENUM_PATTERN = Pattern.compile( - "/\\*\\*\\s+\\*" // Match starting '/**' and the '*' on the next line + "/\\*\\*(\\s+\\*)?" // Match starting '/**' and optional whitespace with a '*' + "(.*?)\\s+\\*/" // Capture everything until we encounter '*/' + "\\s+([A-Z_]+)\\("); // Match the enum name (e.g. 'LOGIN'), until before the first '(' @@ -87,8 +87,8 @@ public class PermissionNodesGatherer { Map allMatches = new HashMap<>(); Matcher matcher = JAVADOC_WITH_ENUM_PATTERN.matcher(source); while (matcher.find()) { - String description = matcher.group(1); - String enumValue = matcher.group(2); + String description = matcher.group(2); + String enumValue = matcher.group(3); allMatches.put(enumValue, description); } return allMatches; diff --git a/src/test/java/tools/filegeneration/GeneratePluginYml.java b/src/test/java/tools/filegeneration/GeneratePluginYml.java index b4a017c6..8fdc779d 100644 --- a/src/test/java/tools/filegeneration/GeneratePluginYml.java +++ b/src/test/java/tools/filegeneration/GeneratePluginYml.java @@ -32,8 +32,9 @@ public class GeneratePluginYml implements AutoToolTask { private static final Map WILDCARD_PERMISSIONS = ImmutableMap.of( "authme.player.*", "Gives access to all player commands", + "authme.player.email", "Gives access to all email commands", "authme.admin.*", "Gives access to all admin commands", - "authme.player.email", "Gives access to all email commands"); + "authme.debug", "Gives access to /authme debug and all its sections"); private List permissionNodes;