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
47 changed files with 786 additions and 306 deletions
+5 -3
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
@@ -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();
@@ -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);
}
}
@@ -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) + ")");
@@ -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) {
@@ -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) {
@@ -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();
}
@@ -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 + "'");
}
}
/**
@@ -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:");
@@ -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.
*/
@@ -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;
}
}
@@ -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.
*
@@ -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)));
@@ -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());
@@ -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())
@@ -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);
}
@@ -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.
@@ -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;
}
}
@@ -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.
@@ -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));
}
}
@@ -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;
}
@@ -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.
*/
@@ -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;
@@ -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\\");
@@ -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;
@@ -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.
*
@@ -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.
*