Merge branch 'master' of https://github.com/AuthMe/AuthMeReloaded into explicit-getters-from-db

This commit is contained in:
ljacqu 2017-04-29 08:19:30 +02:00
commit b7c35cb3a7
47 changed files with 786 additions and 306 deletions

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Wed Mar 22 23:10:32 CET 2017. See docs/commands/commands.tpl.md -->
<!-- File auto-generated on Sun Apr 23 19:29:43 CEST 2017. See docs/commands/commands.tpl.md -->
## 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 (`[ ]`).
<br />Requires `authme.admin.firstspawn`
- **/authme setfirstspawn**: Change the first player's spawn to your current position.
<br />Requires `authme.admin.setfirstspawn`
- **/authme purge** &lt;days> [all]: Purge old AuthMeReloaded data longer than the specified amount of days ago.
- **/authme purge** &lt;days> [all]: Purge old AuthMeReloaded data longer than the specified number of days ago.
<br />Requires `authme.admin.purge`
- **/authme backup**: Creates a backup of the registered users.
<br />Requires `authme.admin.backup`
- **/authme resetpos** &lt;player/*>: Purge the last know position of the specified player or all of them.
<br />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 (`[ ]`).
<br />Requires `authme.admin.converter`
- **/authme messages**: Adds missing messages to the current messages file.
<br />Requires `authme.admin.updatemessages`
- **/authme debug** [child] [params]: Allows various operations for debugging.
<br />Requires `authme.debug`
- **/authme debug** [child] [arg] [arg]: Allows various operations for debugging.
<br />Requires `authme.debug.command`
- **/authme help** [query]: View detailed help for /authme commands.
- **/login** &lt;password>: Command to log in using AuthMeReloaded.
<br />Requires `authme.player.login`
@ -82,6 +84,7 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
<br />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

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Tue Mar 28 21:48:52 CEST 2017. See docs/config/config.tpl.md -->
<!-- File auto-generated on Sun Apr 23 19:30:08 CEST 2017. See docs/config/config.tpl.md -->
## 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

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Oct 23 15:38:58 CEST 2016. See permissions/permission_nodes.tpl.md -->
<!-- File auto-generated on Sun Apr 23 19:32:06 CEST 2017. See docs/permissions/permission_nodes.tpl.md -->
## 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

View File

@ -568,7 +568,7 @@
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-API</artifactId>
<version>4.2.0</version>
<version>4.2.1</version>
<scope>provided</scope>
<exclusions>
<exclusion>

View File

@ -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

View File

@ -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();

View File

@ -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<String> arguments) {
backupService.doBackup(BackupCause.COMMAND, sender);
}
}

View File

@ -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) + ")");

View File

@ -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) {

View File

@ -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<DebugSection> debugSectionFactory;
@Inject
private PermissionsManager permissionsManager;
private Map<String, DebugSection> sections;
@Override
public void executeCommand(CommandSender sender, List<String> 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<String> arguments) {
private DebugSection findDebugSection(List<String> 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<String> 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<String, DebugSection> getSections() {
if (sections == null) {

View File

@ -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<String> arguments);
/**
* @return permission required to run this section
*/
PermissionNode getRequiredPermission();
}

View File

@ -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<Class<? extends PermissionNode>> PERMISSION_NODE_CLASSES =
ImmutableList.of(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class);
static final List<Class<? extends PermissionNode>> 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 + "'");
}
}
/**

View File

@ -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:");

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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.
*

View File

@ -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)));

View File

@ -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<String> arguments) {
if (arguments.isEmpty()) {
DataSourceResult<String> emailResult = dataSource.getEmail(sender.getName());

View File

@ -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())

View File

@ -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);
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -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;

View File

@ -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<Boolean> 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<Boolean> 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<Boolean> ON_SERVER_STOP =
newProperty("BackupSystem.OnServerStop", true);
@Comment("Windows only mysql installation Path")
@Comment("Windows only: MySQL installation path")
public static final Property<String> MYSQL_WINDOWS_PATH =
newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\");

View File

@ -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<OfflinePlayer> cleared) {
if (!settings.getProperty(PurgeSettings.REMOVE_PERMISSIONS)) {
return;

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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 <new password> 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 <nova senha> 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 <senha>'
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 <seuEmail> <seuEmail>"'
recovery_email: '&3Esqueceu sua senha? Por favor, use o comando "/email recovery <seuEmail>"'
# 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 <theCaptcha>"'
@ -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'

View File

@ -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 <new password> 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 <nuevaContraseña>" 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 <tuEmail>'
# 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

View File

@ -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 <password>
@ -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 <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

View File

@ -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<T> extends TypeSafeMatcher<T> {
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 <T> the object's type
* @return the matcher for the expected object
*/
public static <T> Matcher<T> 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<Field> 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<Field> getAllFields(Object object) {
List<Field> 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;
}
}

View File

@ -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<DebugSection> 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<String> 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<String> 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<String> 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<String> 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")));
}
}

View File

@ -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

View File

@ -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 <P extends RegistrationParameters> Matcher<P> isEqualTo(P expected) {
return new TypeSafeMatcher<P>() {
@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<Field> 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<Field> getFields(RegistrationParameters params) {
List<Field> 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;
}
}

View File

@ -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<String> 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();
}

View File

@ -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<String> 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.";
}
}

View File

@ -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.";
}
}

View File

@ -31,7 +31,7 @@ public class PermissionConsistencyTest {
/** Wildcard permissions (present in plugin.yml but not in the codebase). */
private static final Set<String> 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<String> PERMISSION_FIELDS = ImmutableSet.of("description", "default", "children");

View File

@ -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<String> 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.";
}
}

View File

@ -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<String> forbiddenPrefixes = newHashSet("authme.player", "authme.admin");
Set<String> 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<String> 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<String> prefixes) {
@Override
protected String getRequiredPrefix() {
return "authme.";
}
private static boolean startsWithAny(String node, Collection<String> prefixes) {
for (String prefix : prefixes) {
if (node.startsWith(prefix)) {
return true;
@ -51,5 +44,4 @@ public class PlayerStatePermissionTest {
}
return false;
}
}

View File

@ -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);

View File

@ -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<String, String> 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;

View File

@ -32,8 +32,9 @@ public class GeneratePluginYml implements AutoToolTask {
private static final Map<String, String> 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<PermissionNode> permissionNodes;