hamcrest-core
diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java
index 74a986543..f74d56067 100644
--- a/src/main/java/fr/xephi/authme/AuthMe.java
+++ b/src/main/java/fr/xephi/authme/AuthMe.java
@@ -14,6 +14,8 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
import fr.xephi.authme.initialization.OnStartupTasks;
import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser;
+import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
+import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener;
@@ -24,22 +26,22 @@ import fr.xephi.authme.listener.PlayerListener19;
import fr.xephi.authme.listener.ServerListener;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PermissionsSystemType;
-import fr.xephi.authme.security.crypts.SHA256;
+import fr.xephi.authme.security.HashAlgorithm;
+import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.MigrationService;
import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.task.CleanupTask;
import fr.xephi.authme.task.purge.PurgeService;
-import fr.xephi.authme.util.PlayerUtils;
+import org.apache.commons.lang.SystemUtils;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@@ -72,8 +74,6 @@ public class AuthMe extends JavaPlugin {
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
- private GeoIpService geoIpService;
- private PlayerCache playerCache;
/**
* Constructor.
@@ -139,6 +139,7 @@ public class AuthMe extends JavaPlugin {
initialize();
} catch (Exception e) {
ConsoleLogger.logException("Aborting initialization of AuthMe:", e);
+ OnStartupTasks.displayLegacyJarHint(e);
stopOrUnload();
return;
}
@@ -148,7 +149,8 @@ public class AuthMe extends JavaPlugin {
// If server is using PermissionsBukkit, print a warning that some features may not be supported
if (PermissionsSystemType.PERMISSIONS_BUKKIT.equals(permsMan.getPermissionSystem())) {
- ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions features may not be supported!");
+ ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions "
+ + "features may not be supported!");
}
// Do a backup on start
@@ -159,10 +161,12 @@ public class AuthMe extends JavaPlugin {
// Sponsor messages
ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt.");
- ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!");
+ ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader "
+ + "in Italy as Game Server Provider!");
// Successful message
- ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber() + " correctly enabled!");
+ ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber()
+ + " correctly enabled!");
// Purge on start if enabled
PurgeService purgeService = injector.getSingleton(PurgeService.class);
@@ -197,11 +201,19 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.setLogger(getLogger());
ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME));
+ // Check java version
+ if(!SystemUtils.isJavaVersionAtLeast(1.8f)) {
+ throw new IllegalStateException("You need Java 1.8 or above to run this plugin!");
+ }
+
// Create plugin folder
getDataFolder().mkdir();
// Create injector, provide elements from the Bukkit environment and register providers
- injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create();
+ injector = new InjectorBuilder()
+ .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
+ .addDefaultHandlers("fr.xephi.authme")
+ .create();
injector.register(AuthMe.class, this);
injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager());
@@ -219,7 +231,7 @@ public class AuthMe extends JavaPlugin {
instantiateServices(injector);
// Convert deprecated PLAINTEXT hash entries
- MigrationService.changePlainTextToSha256(settings, database, new SHA256());
+ MigrationService.changePlainTextToSha256(settings, database, new Sha256());
// TODO: does this still make sense? -sgdc3
// If the server is empty (fresh start) just set all the players as unlogged
@@ -240,16 +252,15 @@ public class AuthMe extends JavaPlugin {
*
* @param injector the injector
*/
- protected void instantiateServices(Injector injector) {
+ void instantiateServices(Injector injector) {
// PlayerCache is still injected statically sometimes
- playerCache = PlayerCache.getInstance();
+ PlayerCache playerCache = PlayerCache.getInstance();
injector.register(PlayerCache.class, playerCache);
database = injector.getSingleton(DataSource.class);
permsMan = injector.getSingleton(PermissionsManager.class);
bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class);
- geoIpService = injector.getSingleton(GeoIpService.class);
// Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(NewAPI.class);
@@ -270,6 +281,19 @@ public class AuthMe extends JavaPlugin {
&& settings.getProperty(PluginSettings.SESSIONS_ENABLED)) {
ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!");
}
+
+ // Use TLS property only affects port 25
+ if (!settings.getProperty(EmailSettings.PORT25_USE_TLS)
+ && settings.getProperty(EmailSettings.SMTP_PORT) != 25) {
+ ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25");
+ }
+
+ // Unsalted hashes will be deprecated in 5.4 (see Github issue #1016)
+ HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH);
+ if (OnStartupTasks.isHashDeprecatedIn54(hash)) {
+ ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed "
+ + "in 5.4 -- do you still need it? Comment on https://github.com/Xephi/AuthMeReloaded/issues/1016");
+ }
}
/**
@@ -277,7 +301,7 @@ public class AuthMe extends JavaPlugin {
*
* @param injector the injector
*/
- protected void registerEventListeners(Injector injector) {
+ void registerEventListeners(Injector injector) {
// Get the plugin manager instance
PluginManager pluginManager = getServer().getPluginManager();
@@ -344,24 +368,6 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.close();
}
- public String replaceAllInfo(String message, Player player) {
- String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size());
- String ipAddress = PlayerUtils.getPlayerIp(player);
- Server server = getServer();
- return message
- .replace("&", "\u00a7")
- .replace("{PLAYER}", player.getName())
- .replace("{ONLINE}", playersOnline)
- .replace("{MAXPLAYERS}", Integer.toString(server.getMaxPlayers()))
- .replace("{IP}", ipAddress)
- .replace("{LOGINS}", Integer.toString(playerCache.getLogged()))
- .replace("{WORLD}", player.getWorld().getName())
- .replace("{SERVER}", server.getServerName())
- .replace("{VERSION}", server.getBukkitVersion())
- // TODO: We should cache info like this, maybe with a class that extends Player?
- .replace("{COUNTRY}", geoIpService.getCountryName(ipAddress));
- }
-
/**
* Handle Bukkit commands.
*
diff --git a/src/main/java/fr/xephi/authme/ConsoleLogger.java b/src/main/java/fr/xephi/authme/ConsoleLogger.java
index b01674591..9d18820cd 100644
--- a/src/main/java/fr/xephi/authme/ConsoleLogger.java
+++ b/src/main/java/fr/xephi/authme/ConsoleLogger.java
@@ -12,7 +12,10 @@ import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
+import java.util.function.Supplier;
+import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -89,6 +92,18 @@ public final class ConsoleLogger {
writeLog("[WARN] " + message);
}
+ /**
+ * Log a Throwable with the provided message on WARNING level
+ * and save the stack trace to the log file.
+ *
+ * @param message The message to accompany the exception
+ * @param th The Throwable to log
+ */
+ public static void logException(String message, Throwable th) {
+ warning(message + " " + StringUtils.formatException(th));
+ writeLog(Throwables.getStackTraceAsString(th));
+ }
+
/**
* Log an INFO message.
*
@@ -114,6 +129,10 @@ public final class ConsoleLogger {
}
}
+ // --------
+ // Debug log methods
+ // --------
+
/**
* Log a DEBUG message if enabled.
*
@@ -124,21 +143,78 @@ public final class ConsoleLogger {
*/
public static void debug(String message) {
if (logLevel.includes(LogLevel.DEBUG)) {
- logger.info("Debug: " + message);
- writeLog("[DEBUG] " + message);
+ String debugMessage = "[DEBUG] " + message;
+ logger.info(debugMessage);
+ writeLog(debugMessage);
}
}
/**
- * Log a Throwable with the provided message on WARNING level
- * and save the stack trace to the log file.
+ * Log the DEBUG message from the supplier if enabled.
*
- * @param message The message to accompany the exception
- * @param th The Throwable to log
+ * @param msgSupplier the message supplier
*/
- public static void logException(String message, Throwable th) {
- warning(message + " " + StringUtils.formatException(th));
- writeLog(Throwables.getStackTraceAsString(th));
+ public static void debug(Supplier msgSupplier) {
+ if (logLevel.includes(LogLevel.DEBUG)) {
+ String debugMessage = "[DEBUG] " + msgSupplier.get();
+ logger.info(debugMessage);
+ writeLog(debugMessage);
+ }
+ }
+
+ /**
+ * Log the DEBUG message.
+ *
+ * @param message the message
+ * @param param1 parameter to replace in the message
+ */
+ public static void debug(String message, String param1) {
+ if (logLevel.includes(LogLevel.DEBUG)) {
+ String debugMessage = "[DEBUG] " + message;
+ logger.log(Level.INFO, debugMessage, param1);
+ writeLog(debugMessage + " {" + param1 + "}");
+ }
+ }
+
+ /**
+ * Log the DEBUG message.
+ *
+ * @param message the message
+ * @param param1 first param to replace in message
+ * @param param2 second param to replace in message
+ */
+ // Avoids array creation if DEBUG level is disabled
+ public static void debug(String message, String param1, String param2) {
+ if (logLevel.includes(LogLevel.DEBUG)) {
+ debug(message, new String[]{param1, param2});
+ }
+ }
+
+ /**
+ * Log the DEBUG message.
+ *
+ * @param message the message
+ * @param params the params to replace in the message
+ */
+ // Equivalent to debug(String, Object...) but avoids conversions
+ public static void debug(String message, String... params) {
+ if (logLevel.includes(LogLevel.DEBUG)) {
+ String debugMessage = "[DEBUG] " + message;
+ logger.log(Level.INFO, debugMessage, params);
+ writeLog(debugMessage + " {" + String.join(", ", params) + "}");
+ }
+ }
+
+ /**
+ * Log the DEBUG message.
+ *
+ * @param message the message
+ * @param params the params to replace in the message
+ */
+ public static void debug(String message, Object... params) {
+ if (logLevel.includes(LogLevel.DEBUG)) {
+ debug(message, Arrays.stream(params).map(String::valueOf).toArray(String[]::new));
+ }
}
diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java
index d05fbe6b9..75e746e6d 100644
--- a/src/main/java/fr/xephi/authme/api/API.java
+++ b/src/main/java/fr/xephi/authme/api/API.java
@@ -23,6 +23,7 @@ import javax.inject.Inject;
* @deprecated Use {@link NewAPI}
*/
@Deprecated
+@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
public class API {
private static AuthMe instance;
diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java
index d4f34b2c6..62f1fefc9 100644
--- a/src/main/java/fr/xephi/authme/api/NewAPI.java
+++ b/src/main/java/fr/xephi/authme/api/NewAPI.java
@@ -5,7 +5,8 @@ import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.process.Management;
-import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
+import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
+import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.PluginHookService;
@@ -24,6 +25,7 @@ import java.util.List;
* NewAPI authmeApi = AuthMe.getApi();
*
*/
+@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
public class NewAPI {
private static NewAPI singleton;
@@ -34,15 +36,13 @@ public class NewAPI {
private final Management management;
private final ValidationService validationService;
private final PlayerCache playerCache;
- private final RegistrationExecutorProvider registrationExecutorProvider;
/*
* Constructor for NewAPI.
*/
@Inject
NewAPI(AuthMe plugin, PluginHookService pluginHookService, DataSource dataSource, PasswordSecurity passwordSecurity,
- Management management, ValidationService validationService, PlayerCache playerCache,
- RegistrationExecutorProvider registrationExecutorProvider) {
+ Management management, ValidationService validationService, PlayerCache playerCache) {
this.plugin = plugin;
this.pluginHookService = pluginHookService;
this.dataSource = dataSource;
@@ -50,7 +50,6 @@ public class NewAPI {
this.management = management;
this.validationService = validationService;
this.playerCache = playerCache;
- this.registrationExecutorProvider = registrationExecutorProvider;
NewAPI.singleton = this;
}
@@ -128,7 +127,8 @@ public class NewAPI {
public Location getLastLocation(Player player) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth != null) {
- return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
+ return new Location(Bukkit.getWorld(auth.getWorld()),
+ auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
}
return null;
}
@@ -203,8 +203,8 @@ public class NewAPI {
* @param autoLogin Should the player be authenticated automatically after the registration?
*/
public void forceRegister(Player player, String password, boolean autoLogin) {
- management.performRegister(player,
- registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin));
+ management.performRegister(RegistrationMethod.API_REGISTRATION,
+ ApiPasswordRegisterParams.of(player, password, autoLogin));
}
/**
diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java
index b6c006f9b..e195c4752 100644
--- a/src/main/java/fr/xephi/authme/command/CommandDescription.java
+++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java
@@ -1,8 +1,8 @@
package fr.xephi.authme.command;
import fr.xephi.authme.permission.PermissionNode;
-import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
+import fr.xephi.authme.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -19,6 +19,7 @@ import static java.util.Arrays.asList;
* {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that
* the child defines.
*/
+@SuppressWarnings("checkstyle:FinalClass") // Justification: class is mocked in multiple tests
public class CommandDescription {
/**
@@ -224,7 +225,7 @@ public class CommandDescription {
* @return The generated CommandDescription object
*/
public CommandDescription build() {
- checkArgument(!CollectionUtils.isEmpty(labels), "Labels may not be empty");
+ checkArgument(!Utils.isCollectionEmpty(labels), "Labels may not be empty");
checkArgument(!StringUtils.isEmpty(description), "Description may not be empty");
checkArgument(!StringUtils.isEmpty(detailedDescription), "Detailed description may not be empty");
checkArgument(executableCommand != null, "Executable command must be set");
diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java
index 394b6f144..083d8a531 100644
--- a/src/main/java/fr/xephi/authme/command/CommandHandler.java
+++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java
@@ -1,8 +1,8 @@
package fr.xephi.authme.command;
-import ch.jalu.injector.Injector;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
+import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;
@@ -40,13 +40,13 @@ public class CommandHandler {
private Map, ExecutableCommand> commands = new HashMap<>();
@Inject
- CommandHandler(Injector injector, CommandMapper commandMapper, PermissionsManager permissionsManager,
- Messages messages, HelpProvider helpProvider) {
+ CommandHandler(Factory commandFactory, CommandMapper commandMapper,
+ PermissionsManager permissionsManager, Messages messages, HelpProvider helpProvider) {
this.commandMapper = commandMapper;
this.permissionsManager = permissionsManager;
this.messages = messages;
this.helpProvider = helpProvider;
- initializeCommands(injector, commandMapper.getCommandClasses());
+ initializeCommands(commandFactory, commandMapper.getCommandClasses());
}
/**
@@ -94,13 +94,13 @@ public class CommandHandler {
/**
* Initialize all required ExecutableCommand objects.
*
- * @param injector the injector
+ * @param commandFactory factory to create command objects
* @param commandClasses the classes to instantiate
*/
- private void initializeCommands(Injector injector,
+ private void initializeCommands(Factory commandFactory,
Set> commandClasses) {
for (Class extends ExecutableCommand> clazz : commandClasses) {
- commands.put(clazz, injector.newInstance(clazz));
+ commands.put(clazz, commandFactory.newInstance(clazz));
}
}
diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
index 7e01e0e30..dd27c2794 100644
--- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java
+++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
@@ -24,12 +24,15 @@ import fr.xephi.authme.command.executable.authme.SpawnCommand;
import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand;
import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand;
import fr.xephi.authme.command.executable.authme.VersionCommand;
+import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.command.executable.captcha.CaptchaCommand;
import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand;
import fr.xephi.authme.command.executable.email.AddEmailCommand;
import fr.xephi.authme.command.executable.email.ChangeEmailCommand;
import fr.xephi.authme.command.executable.email.EmailBaseCommand;
+import fr.xephi.authme.command.executable.email.ProcessCodeCommand;
import fr.xephi.authme.command.executable.email.RecoverEmailCommand;
+import fr.xephi.authme.command.executable.email.SetPasswordCommand;
import fr.xephi.authme.command.executable.email.ShowEmailCommand;
import fr.xephi.authme.command.executable.login.LoginCommand;
import fr.xephi.authme.command.executable.logout.LogoutCommand;
@@ -37,6 +40,7 @@ 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.PlayerPermission;
+import fr.xephi.authme.permission.PlayerStatePermission;
import java.util.Arrays;
import java.util.Collection;
@@ -62,6 +66,10 @@ public class CommandInitializer {
return commands;
}
+ /**
+ * Builds the command description objects for all available AuthMe commands.
+ */
+ @SuppressWarnings({"checkstyle:LocalVariableName", "checkstyle:AbbreviationAsWordInName"})
private void buildCommands() {
// Register the base AuthMe Reloaded command
final CommandDescription AUTHME_BASE = CommandDescription.builder()
@@ -283,8 +291,8 @@ public class CommandInitializer {
.labels("converter", "convert", "conv")
.description("Converter command")
.detailedDescription("Converter command for AuthMeReloaded.")
- .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " +
- "royalauth / vauth / sqliteToSql / mysqlToSqlite", false)
+ .withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
+ + "royalauth / vauth / sqliteToSql / mysqlToSqlite", false)
.permission(AdminPermission.CONVERTER)
.executableCommand(ConverterCommand.class)
.register();
@@ -298,6 +306,19 @@ public class CommandInitializer {
.executableCommand(MessagesCommand.class)
.register();
+ CommandDescription.builder()
+ .parent(AUTHME_BASE)
+ .labels("debug", "dbg")
+ .description("Debug features")
+ .detailedDescription("Allows various operations for debugging.")
+ .withArgument("child", "The child to execute", true)
+ .withArgument(".", "meaning varies", true)
+ .withArgument(".", "meaning varies", true)
+ .withArgument(".", "meaning varies", true)
+ .permission(PlayerStatePermission.DEBUG_COMMAND)
+ .executableCommand(DebugCommand.class)
+ .register();
+
// Register the base login command
final CommandDescription LOGIN_BASE = CommandDescription.builder()
.parent(null)
@@ -401,14 +422,35 @@ public class CommandInitializer {
.parent(EMAIL_BASE)
.labels("recover", "recovery", "recoveremail", "recovermail")
.description("Recover password using email")
- .detailedDescription("Recover your account using an Email address by sending a mail containing " +
- "a new password.")
+ .detailedDescription("Recover your account using an Email address by sending a mail containing "
+ + "a new password.")
.withArgument("email", "Email address", false)
- .withArgument("code", "Recovery code", true)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(RecoverEmailCommand.class)
.register();
+ // Register the process recovery code command
+ CommandDescription.builder()
+ .parent(EMAIL_BASE)
+ .labels("code")
+ .description("Submit code to recover password")
+ .detailedDescription("Recover your account by submitting a code delivered to your email.")
+ .withArgument("code", "Recovery code", false)
+ .permission(PlayerPermission.RECOVER_EMAIL)
+ .executableCommand(ProcessCodeCommand.class)
+ .register();
+
+ // Register the change password after recovery command
+ CommandDescription.builder()
+ .parent(EMAIL_BASE)
+ .labels("setpassword")
+ .description("Set new password after recovery")
+ .detailedDescription("Set a new password after successfully recovering your account.")
+ .withArgument("password", "New password", false)
+ .permission(PlayerPermission.RECOVER_EMAIL)
+ .executableCommand(SetPasswordCommand.class)
+ .register();
+
// Register the base captcha command
CommandDescription CAPTCHA_BASE = CommandDescription.builder()
.parent(null)
diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java
index 8e2b1bdc1..33003c3ae 100644
--- a/src/main/java/fr/xephi/authme/command/CommandMapper.java
+++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java
@@ -2,8 +2,8 @@ package fr.xephi.authme.command;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.permission.PermissionsManager;
-import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
+import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
@@ -45,7 +45,7 @@ public class CommandMapper {
* @return The generated {@link FoundCommandResult}
*/
public FoundCommandResult mapPartsToCommand(CommandSender sender, final List parts) {
- if (CollectionUtils.isEmpty(parts)) {
+ if (Utils.isCollectionEmpty(parts)) {
return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND);
}
@@ -123,6 +123,9 @@ public class CommandMapper {
private CommandDescription getBaseCommand(String label) {
String baseLabel = label.toLowerCase();
+ if (baseLabel.startsWith("authme:")) {
+ baseLabel = baseLabel.substring("authme:".length());
+ }
for (CommandDescription command : baseCommands) {
if (command.hasLabel(baseLabel)) {
return command;
@@ -142,7 +145,7 @@ public class CommandMapper {
* @return A command if there was a complete match (including proper argument count), null otherwise
*/
private static CommandDescription getSuitableChild(CommandDescription baseCommand, List parts) {
- if (CollectionUtils.isEmpty(parts)) {
+ if (Utils.isCollectionEmpty(parts)) {
return null;
}
diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java
index 6dd81ca80..54ee9e846 100644
--- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java
+++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java
@@ -19,7 +19,7 @@ public interface ExecutableCommand {
void executeCommand(CommandSender sender, List arguments);
/**
- * Returns the message to show to the user if the command is used with the wrong commands.
+ * Returns the message to show to the user if the command is used with the wrong arguments.
* If null is returned, the standard help (/command help) output is shown.
*
* @return the message explaining the command's usage, or {@code null} for default behavior
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java
index efa27ff09..c59c4cb94 100644
--- a/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java
@@ -1,6 +1,5 @@
package fr.xephi.authme.command.executable.authme;
-import ch.jalu.injector.Injector;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
@@ -11,8 +10,9 @@ import fr.xephi.authme.datasource.converter.MySqlToSqlite;
import fr.xephi.authme.datasource.converter.RakamakConverter;
import fr.xephi.authme.datasource.converter.RoyalAuthConverter;
import fr.xephi.authme.datasource.converter.SqliteToSql;
-import fr.xephi.authme.datasource.converter.vAuthConverter;
-import fr.xephi.authme.datasource.converter.xAuthConverter;
+import fr.xephi.authme.datasource.converter.VAuthConverter;
+import fr.xephi.authme.datasource.converter.XAuthConverter;
+import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
@@ -37,7 +37,7 @@ public class ConverterCommand implements ExecutableCommand {
private BukkitService bukkitService;
@Inject
- private Injector injector;
+ private Factory converterFactory;
@Override
public void executeCommand(final CommandSender sender, List arguments) {
@@ -52,7 +52,7 @@ public class ConverterCommand implements ExecutableCommand {
}
// Get the proper converter instance
- final Converter converter = injector.newInstance(converterClass);
+ final Converter converter = converterFactory.newInstance(converterClass);
// Run the convert job
bukkitService.runTaskAsynchronously(new Runnable() {
@@ -78,11 +78,11 @@ public class ConverterCommand implements ExecutableCommand {
*/
private static Map> getConverters() {
return ImmutableMap.>builder()
- .put("xauth", xAuthConverter.class)
+ .put("xauth", XAuthConverter.class)
.put("crazylogin", CrazyLoginConverter.class)
.put("rakamak", RakamakConverter.class)
.put("royalauth", RoyalAuthConverter.class)
- .put("vauth", vAuthConverter.class)
+ .put("vauth", VAuthConverter.class)
.put("sqlitetosql", SqliteToSql.class)
.put("mysqltosqlite", MySqlToSqlite.class)
.build();
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java
index cba5edf0d..2f1341b92 100644
--- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java
@@ -1,9 +1,8 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.data.auth.PlayerAuth;
-import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.command.ExecutableCommand;
+import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
@@ -38,9 +37,6 @@ public class RegisterAdminCommand implements ExecutableCommand {
@Inject
private ValidationService validationService;
- @Inject
- private LimboCache limboCache;
-
@Override
public void executeCommand(final CommandSender sender, List arguments) {
// Get the player name and password
@@ -83,7 +79,6 @@ public class RegisterAdminCommand implements ExecutableCommand {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
@Override
public void run() {
- limboCache.restoreData(player);
player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER));
}
});
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java
index 037846aaf..af0aaaca2 100644
--- a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java
@@ -1,20 +1,20 @@
package fr.xephi.authme.command.executable.authme;
-import ch.jalu.injector.Injector;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
+import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
+import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
-import java.util.Collection;
import java.util.List;
/**
@@ -25,9 +25,6 @@ public class ReloadCommand implements ExecutableCommand {
@Inject
private AuthMe plugin;
- @Inject
- private Injector injector;
-
@Inject
private Settings settings;
@@ -37,6 +34,12 @@ public class ReloadCommand implements ExecutableCommand {
@Inject
private CommonService commonService;
+ @Inject
+ private SingletonStore reloadableStore;
+
+ @Inject
+ private SingletonStore settingsDependentStore;
+
@Override
public void executeCommand(CommandSender sender, List arguments) {
try {
@@ -44,8 +47,7 @@ public class ReloadCommand implements ExecutableCommand {
ConsoleLogger.setLoggingOptions(settings);
// We do not change database type for consistency issues, but we'll output a note in the logs
if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) {
- ConsoleLogger.info("Note: cannot change database type during /authme reload");
- sender.sendMessage("Note: cannot change database type during /authme reload");
+ Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload");
}
performReloadOnServices();
commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
@@ -57,14 +59,10 @@ public class ReloadCommand implements ExecutableCommand {
}
private void performReloadOnServices() {
- Collection reloadables = injector.retrieveAllOfType(Reloadable.class);
- for (Reloadable reloadable : reloadables) {
- reloadable.reload();
- }
+ reloadableStore.retrieveAllOfType()
+ .forEach(r -> r.reload());
- Collection settingsDependents = injector.retrieveAllOfType(SettingsDependent.class);
- for (SettingsDependent dependent : settingsDependents) {
- dependent.reload(settings);
- }
+ settingsDependentStore.retrieveAllOfType()
+ .forEach(s -> s.reload(settings));
}
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java
index 791cefe0e..d042c9a65 100644
--- a/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/VersionCommand.java
@@ -31,12 +31,12 @@ public class VersionCommand implements ExecutableCommand {
printDeveloper(sender, "games647", "games647", "Developer", onlinePlayers);
printDeveloper(sender, "Tim Visee", "timvisee", "Developer", onlinePlayers);
printDeveloper(sender, "Gabriele C.", "sgdc3", "Project manager, Contributor", onlinePlayers);
- sender.sendMessage(ChatColor.GOLD + "Website: " + ChatColor.WHITE +
- "http://dev.bukkit.org/bukkit-plugins/authme-reloaded/");
+ sender.sendMessage(ChatColor.GOLD + "Website: " + ChatColor.WHITE
+ + "http://dev.bukkit.org/bukkit-plugins/authme-reloaded/");
sender.sendMessage(ChatColor.GOLD + "License: " + ChatColor.WHITE + "GNU GPL v3.0"
+ ChatColor.GRAY + ChatColor.ITALIC + " (See LICENSE file)");
sender.sendMessage(ChatColor.GOLD + "Copyright: " + ChatColor.WHITE
- + "Copyright (c) AuthMe-Team 2016. All rights reserved.");
+ + "Copyright (c) AuthMe-Team 2017. All rights reserved.");
}
/**
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java
new file mode 100644
index 000000000..05de8ab13
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/CountryLookup.java
@@ -0,0 +1,77 @@
+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.service.GeoIpService;
+import fr.xephi.authme.service.ValidationService;
+import fr.xephi.authme.settings.properties.ProtectionSettings;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Shows the GeoIP information as returned by the geoIpService.
+ */
+class CountryLookup implements DebugSection {
+
+ private static final Pattern IS_IP_ADDR = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}");
+
+ @Inject
+ private GeoIpService geoIpService;
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private ValidationService validationService;
+
+ @Override
+ public String getName() {
+ return "cty";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Check country protection / country data";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.isEmpty()) {
+ sender.sendMessage("Check player: /authme debug cty Bobby");
+ sender.sendMessage("Check IP address: /authme debug cty 127.123.45.67");
+ return;
+ }
+
+ String argument = arguments.get(0);
+ if (IS_IP_ADDR.matcher(argument).matches()) {
+ outputInfoForIpAddr(sender, argument);
+ } else {
+ outputInfoForPlayer(sender, argument);
+ }
+ }
+
+ private void outputInfoForIpAddr(CommandSender sender, String ipAddr) {
+ sender.sendMessage("IP '" + ipAddr + "' maps to country '" + geoIpService.getCountryCode(ipAddr)
+ + "' (" + geoIpService.getCountryName(ipAddr) + ")");
+ if (validationService.isCountryAdmitted(ipAddr)) {
+ sender.sendMessage(ChatColor.DARK_GREEN + "This IP address' country is not blocked");
+ } else {
+ sender.sendMessage(ChatColor.DARK_RED + "This IP address' country is blocked from the server");
+ }
+ sender.sendMessage("Note: if " + ProtectionSettings.ENABLE_PROTECTION + " is false no country is blocked");
+ }
+
+ private void outputInfoForPlayer(CommandSender sender, String name) {
+ PlayerAuth auth = dataSource.getAuth(name);
+ if (auth == null) {
+ sender.sendMessage("No player with name '" + name + "'");
+ } else {
+ sender.sendMessage("Player '" + name + "' has IP address " + auth.getIp());
+ outputInfoForIpAddr(sender, auth.getIp());
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java
new file mode 100644
index 000000000..3bf28e057
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DataStatistics.java
@@ -0,0 +1,72 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import fr.xephi.authme.data.auth.PlayerCache;
+import fr.xephi.authme.data.limbo.LimboService;
+import fr.xephi.authme.datasource.CacheDataSource;
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.initialization.HasCleanup;
+import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.initialization.SettingsDependent;
+import fr.xephi.authme.initialization.factory.SingletonStore;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Map;
+
+import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap;
+
+/**
+ * Fetches various statistics, particularly regarding in-memory data that is stored.
+ */
+class DataStatistics implements DebugSection {
+
+ @Inject
+ private PlayerCache playerCache;
+
+ @Inject
+ private LimboService limboService;
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private SingletonStore singletonStore;
+
+ @Override
+ public String getName() {
+ return "stats";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Outputs general data statistics";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ sender.sendMessage("LimboPlayers in memory: " + applyToLimboPlayersMap(limboService, Map::size));
+ sender.sendMessage("PlayerCache size: " + playerCache.getLogged() + " (= logged in players)");
+
+ outputDatabaseStats(sender);
+ outputInjectorStats(sender);
+ }
+
+ private void outputDatabaseStats(CommandSender sender) {
+ sender.sendMessage("Total players in DB: " + dataSource.getAccountsRegistered());
+ sender.sendMessage("Total marked as logged in in DB: " + dataSource.getLoggedPlayers().size());
+ if (dataSource instanceof CacheDataSource) {
+ CacheDataSource cacheDataSource = (CacheDataSource) this.dataSource;
+ sender.sendMessage("Cached PlayerAuth objects: " + cacheDataSource.getCachedAuths().size());
+ }
+ }
+
+ private void outputInjectorStats(CommandSender sender) {
+ sender.sendMessage(
+ String.format("Singleton Java classes: %d (Reloadable: %d / SettingsDependent: %d / HasCleanup: %d)",
+ singletonStore.retrieveAllOfType().size(),
+ singletonStore.retrieveAllOfType(Reloadable.class).size(),
+ singletonStore.retrieveAllOfType(SettingsDependent.class).size(),
+ singletonStore.retrieveAllOfType(HasCleanup.class).size()));
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java
new file mode 100644
index 000000000..fc7cd4291
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java
@@ -0,0 +1,60 @@
+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 org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Debug command main.
+ */
+public class DebugCommand implements ExecutableCommand {
+
+ private static final Set> SECTION_CLASSES = ImmutableSet.of(
+ PermissionGroups.class, DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, InputValidator.class,
+ LimboPlayerViewer.class, CountryLookup.class, HasPermissionChecker.class, TestEmailSender.class,
+ SpawnLocationViewer.class);
+
+ @Inject
+ private Factory debugSectionFactory;
+
+ private Map sections;
+
+ @Override
+ public void executeCommand(CommandSender sender, List arguments) {
+ DebugSection debugSection = getDebugSection(arguments);
+ if (debugSection == null) {
+ sender.sendMessage("Available sections:");
+ getSections().values()
+ .forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription()));
+ } else {
+ debugSection.execute(sender, arguments.subList(1, arguments.size()));
+ }
+ }
+
+ private DebugSection getDebugSection(List arguments) {
+ if (arguments.isEmpty()) {
+ return null;
+ }
+ return getSections().get(arguments.get(0).toLowerCase());
+ }
+
+ // Lazy getter
+ private Map getSections() {
+ if (sections == null) {
+ Map sections = new TreeMap<>();
+ for (Class extends DebugSection> sectionClass : SECTION_CLASSES) {
+ DebugSection section = debugSectionFactory.newInstance(sectionClass);
+ sections.put(section.getName(), section);
+ }
+ this.sections = sections;
+ }
+ return sections;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java
new file mode 100644
index 000000000..1f0389833
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSection.java
@@ -0,0 +1,30 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import org.bukkit.command.CommandSender;
+
+import java.util.List;
+
+/**
+ * A debug section: "child" command of the debug command.
+ */
+interface DebugSection {
+
+ /**
+ * @return the name to get to this child command
+ */
+ String getName();
+
+ /**
+ * @return short description of the child command
+ */
+ String getDescription();
+
+ /**
+ * Executes the debug child command.
+ *
+ * @param sender the sender executing the command
+ * @param arguments the arguments, without the label of the child command
+ */
+ void execute(CommandSender sender, List arguments);
+
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java
new file mode 100644
index 000000000..d98ce746c
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugSectionUtils.java
@@ -0,0 +1,101 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboService;
+import org.bukkit.Location;
+
+import java.lang.reflect.Field;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Utilities used within the DebugSection implementations.
+ */
+final class DebugSectionUtils {
+
+ private static Field limboEntriesField;
+
+ private DebugSectionUtils() {
+ }
+
+ /**
+ * Formats the given location in a human readable way. Null-safe.
+ *
+ * @param location the location to format
+ * @return the formatted location
+ */
+ static String formatLocation(Location location) {
+ if (location == null) {
+ return "null";
+ }
+
+ String worldName = location.getWorld() == null ? "null" : location.getWorld().getName();
+ return formatLocation(location.getX(), location.getY(), location.getZ(), worldName);
+ }
+
+ /**
+ * Formats the given location in a human readable way.
+ *
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param z the z coordinate
+ * @param world the world name
+ * @return the formatted location
+ */
+ static String formatLocation(double x, double y, double z, String world) {
+ return "(" + round(x) + ", " + round(y) + ", " + round(z) + ") in '" + world + "'";
+ }
+
+ /**
+ * Rounds the given number to two decimals.
+ *
+ * @param number the number to round
+ * @return the rounded number
+ */
+ private static String round(double number) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US));
+ df.setRoundingMode(RoundingMode.HALF_UP);
+ return df.format(number);
+ }
+
+ private static Field getLimboPlayerEntriesField() {
+ if (limboEntriesField == null) {
+ try {
+ Field field = LimboService.class.getDeclaredField("entries");
+ field.setAccessible(true);
+ limboEntriesField = field;
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not retrieve LimboService entries field:", e);
+ }
+ }
+ return limboEntriesField;
+ }
+
+ /**
+ * Applies the given function to the map in LimboService containing the LimboPlayers.
+ * As we don't want to expose this information in non-debug settings, this is done with reflection.
+ * Exceptions are generously caught and {@code null} is returned on failure.
+ *
+ * @param limboService the limbo service instance to get the map from
+ * @param function the function to apply to the map
+ * @param the result type of the function
+ *
+ * @return player names for which there is a LimboPlayer (or error message upon failure)
+ */
+ static U applyToLimboPlayersMap(LimboService limboService, Function function) {
+ Field limboPlayerEntriesField = getLimboPlayerEntriesField();
+ if (limboPlayerEntriesField != null) {
+ try {
+ return function.apply((Map) limboEntriesField.get(limboService));
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not retrieve LimboService values:", e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java
new file mode 100644
index 000000000..436ef180c
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/HasPermissionChecker.java
@@ -0,0 +1,131 @@
+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.DefaultPermission;
+import fr.xephi.authme.permission.PermissionNode;
+import fr.xephi.authme.permission.PermissionsManager;
+import fr.xephi.authme.permission.PlayerPermission;
+import fr.xephi.authme.permission.PlayerStatePermission;
+import fr.xephi.authme.service.BukkitService;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiFunction;
+
+/**
+ * Checks if a player has a given permission, as checked by AuthMe.
+ */
+class HasPermissionChecker implements DebugSection {
+
+ static final List> PERMISSION_NODE_CLASSES =
+ ImmutableList.of(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class);
+
+ @Inject
+ private PermissionsManager permissionsManager;
+
+ @Inject
+ private BukkitService bukkitService;
+
+ @Override
+ public String getName() {
+ return "perm";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Checks if player has given permission: /authme debug perm bobby my.perm";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.size() < 2) {
+ sender.sendMessage("Check if a player has permission:");
+ sender.sendMessage("Example: /authme debug perm bobby my.perm.node");
+ return;
+ }
+
+ final String playerName = arguments.get(0);
+ final String permissionNode = arguments.get(1);
+
+ Player player = bukkitService.getPlayerExact(playerName);
+ if (player == null) {
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
+ if (offlinePlayer == null) {
+ sender.sendMessage(ChatColor.DARK_RED + "Player '" + playerName + "' does not exist");
+ } else {
+ sender.sendMessage("Player '" + playerName + "' not online; checking with offline player");
+ performPermissionCheck(offlinePlayer, permissionNode, permissionsManager::hasPermissionOffline, sender);
+ }
+ } else {
+ performPermissionCheck(player, permissionNode, permissionsManager::hasPermission, sender);
+ }
+ }
+
+ /**
+ * 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}.
+ *
+ * @param player the player to check a permission for
+ * @param node the node of the permission to check
+ * @param permissionChecker permission checking function
+ * @param sender the sender to inform of the result
+ * @param the player type
+ */
+ private static
void performPermissionCheck(
+ P player, String node, BiFunction
permissionChecker, CommandSender sender) {
+
+ PermissionNode permNode = getPermissionNode(sender, node);
+ if (permissionChecker.apply(player, permNode)) {
+ sender.sendMessage(ChatColor.DARK_GREEN + "Success: player '" + player.getName()
+ + "' has permission '" + node + "'");
+ } else {
+ sender.sendMessage(ChatColor.DARK_RED + "Check failed: player '" + player.getName()
+ + "' does NOT have permission '" + node + "'");
+ }
+
+ }
+
+ /**
+ * Based on the given permission node (String), tries to find the according AuthMe {@link PermissionNode}
+ * instance, or creates a new one if not available.
+ *
+ * @param sender the sender (used to inform him if no AuthMe PermissionNode can be matched)
+ * @param node the node to search for
+ * @return the node as {@link PermissionNode} object
+ */
+ private static PermissionNode getPermissionNode(CommandSender sender, String node) {
+ Optional extends PermissionNode> permNode = PERMISSION_NODE_CLASSES.stream()
+ .map(Class::getEnumConstants)
+ .flatMap(Arrays::stream)
+ .filter(perm -> perm.getNode().equals(node))
+ .findFirst();
+ if (permNode.isPresent()) {
+ return permNode.get();
+ } else {
+ sender.sendMessage("Did not detect AuthMe permission; using default permission = DENIED");
+ return createPermNode(node);
+ }
+ }
+
+ private static PermissionNode createPermNode(String node) {
+ return new PermissionNode() {
+ @Override
+ public String getNode() {
+ return node;
+ }
+
+ @Override
+ public DefaultPermission getDefaultPermission() {
+ return DefaultPermission.NOT_ALLOWED;
+ }
+ };
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java
new file mode 100644
index 000000000..472025060
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/InputValidator.java
@@ -0,0 +1,115 @@
+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.service.ValidationService;
+import fr.xephi.authme.service.ValidationService.ValidationResult;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.Arrays;
+import java.util.List;
+
+import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.MAIL;
+import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.NAME;
+import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.PASS;
+
+/**
+ * Checks if a sample username, email or password is valid according to the AuthMe settings.
+ */
+class InputValidator implements DebugSection {
+
+ @Inject
+ private ValidationService validationService;
+
+ @Inject
+ private Messages messages;
+
+ @Inject
+ private OnJoinVerifier onJoinVerifier;
+
+
+ @Override
+ public String getName() {
+ return "valid";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Check if email / password is valid according to your settings";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.size() < 2 || !ValidationObject.matchesAny(arguments.get(0))) {
+ displayUsageHint(sender);
+
+ } else if (PASS.matches(arguments.get(0))) {
+ validatePassword(sender, arguments.get(1));
+
+ } else if (MAIL.matches(arguments.get(0))) {
+ validateEmail(sender, arguments.get(1));
+
+ } else if (NAME.matches(arguments.get(0))) {
+ validateUsername(sender, arguments.get(1));
+
+ } else {
+ throw new IllegalStateException("Unexpected validation object with arg[0] = '" + arguments.get(0) + "'");
+ }
+ }
+
+ 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:");
+ sender.sendMessage("/authme debug valid pass test1234 -- test if 'test1234' is allowed password");
+ sender.sendMessage("/authme debug valid mail t@t.tld -- test if 't@t.tld' is allowed email");
+ sender.sendMessage("/authme debug valid name bobby1 -- test if 'bobby1' is allowed username");
+ }
+
+ private void validatePassword(CommandSender sender, String password) {
+ ValidationResult validationResult = validationService.validatePassword(password, "");
+ sender.sendMessage("Validation of password '" + password + "' returned:");
+ if (validationResult.hasError()) {
+ messages.send(sender, validationResult.getMessageKey(), validationResult.getArgs());
+ } else {
+ sender.sendMessage(ChatColor.DARK_GREEN + "Valid password!");
+ }
+ }
+
+ private void validateEmail(CommandSender sender, String email) {
+ boolean isValidEmail = validationService.validateEmail(email);
+ sender.sendMessage("Validation of email '" + email + "' returned:");
+ if (isValidEmail) {
+ sender.sendMessage(ChatColor.DARK_GREEN + "Valid email!");
+ } else {
+ sender.sendMessage(ChatColor.DARK_RED + "Email is not valid!");
+ }
+ }
+
+ private void validateUsername(CommandSender sender, String username) {
+ sender.sendMessage("Validation of username '" + username + "' returned:");
+ try {
+ onJoinVerifier.checkIsValidName(username);
+ sender.sendMessage("Valid username!");
+ } catch (FailedVerificationException failedVerificationEx) {
+ messages.send(sender, failedVerificationEx.getReason(), failedVerificationEx.getArgs());
+ }
+ }
+
+
+ enum ValidationObject {
+
+ PASS, MAIL, NAME;
+
+ static boolean matchesAny(String arg) {
+ return Arrays.stream(values()).anyMatch(vo -> vo.matches(arg));
+ }
+
+ boolean matches(String arg) {
+ return name().equalsIgnoreCase(arg);
+ }
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java
new file mode 100644
index 000000000..a8c62e683
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/LimboPlayerViewer.java
@@ -0,0 +1,132 @@
+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.PermissionsManager;
+import fr.xephi.authme.service.BukkitService;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
+import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap;
+
+/**
+ * Shows the data stored in LimboPlayers and the equivalent properties on online players.
+ */
+class LimboPlayerViewer implements DebugSection {
+
+ @Inject
+ private LimboService limboService;
+
+ @Inject
+ private LimboPersistence limboPersistence;
+
+ @Inject
+ private BukkitService bukkitService;
+
+ @Inject
+ private PermissionsManager permissionsManager;
+
+ @Override
+ public String getName() {
+ return "limbo";
+ }
+
+ @Override
+ public String getDescription() {
+ return "View LimboPlayers and player's \"limbo stats\"";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.isEmpty()) {
+ sender.sendMessage("/authme debug limbo : show a player's limbo info");
+ sender.sendMessage("Available limbo records: " + applyToLimboPlayersMap(limboService, Map::keySet));
+ return;
+ }
+
+ LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0));
+ Player player = bukkitService.getPlayerExact(arguments.get(0));
+ LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null;
+ if (memoryLimbo == null && player == null) {
+ sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'");
+ return;
+ }
+
+ sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'");
+ new InfoDisplayer(sender, diskLimbo, memoryLimbo, player)
+ .sendEntry("Is op", LimboPlayer::isOperator, Player::isOp)
+ .sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed)
+ .sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight)
+ .sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed)
+ .sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation()))
+ .sendEntry("Group", LimboPlayer::getGroup, permissionsManager::getPrimaryGroup);
+ }
+
+ /**
+ * Displays the info for the given LimboPlayer and Player to the provided CommandSender.
+ */
+ private static final class InfoDisplayer {
+ private final CommandSender sender;
+ private final Optional diskLimbo;
+ private final Optional memoryLimbo;
+ private final Optional player;
+
+ /**
+ * Constructor.
+ *
+ * @param sender command sender to send the information to
+ * @param memoryLimbo the limbo player to get data from
+ * @param player the player to get data from
+ */
+ InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) {
+ this.sender = sender;
+ this.diskLimbo = Optional.ofNullable(diskLimbo);
+ this.memoryLimbo = Optional.ofNullable(memoryLimbo);
+ this.player = Optional.ofNullable(player);
+
+ if (memoryLimbo == null) {
+ sender.sendMessage("Note: no Limbo information available");
+ }
+ if (player == null) {
+ sender.sendMessage("Note: player is not online");
+ } else if (diskLimbo == null) {
+ sender.sendMessage("Note: no Limbo on disk available");
+ }
+ }
+
+ /**
+ * Displays a piece of information to the command sender.
+ *
+ * @param title the designation of the piece of information
+ * @param limboGetter getter for data retrieval on the LimboPlayer
+ * @param playerGetter getter for data retrieval on Player
+ * @param the data type
+ * @return this instance (for chaining)
+ */
+ InfoDisplayer sendEntry(String title,
+ Function limboGetter,
+ Function playerGetter) {
+ sender.sendMessage(
+ title + ": "
+ + getData(diskLimbo, limboGetter)
+ + " / "
+ + getData(memoryLimbo, limboGetter)
+ + " / "
+ + getData(player, playerGetter));
+ return this;
+ }
+
+ static String getData(Optional entity, Function getter) {
+ return entity.map(getter).map(String::valueOf).orElse(" -- ");
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java
new file mode 100644
index 000000000..a7bf19eb1
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java
@@ -0,0 +1,41 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import fr.xephi.authme.permission.PermissionsManager;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Outputs the permission groups of a player.
+ */
+class PermissionGroups implements DebugSection {
+
+ @Inject
+ private PermissionsManager permissionsManager;
+
+ @Override
+ public String getName() {
+ return "groups";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Show permission groups a player belongs to";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ String name = arguments.isEmpty() ? sender.getName() : arguments.get(0);
+ Player player = Bukkit.getPlayer(name);
+ if (player == null) {
+ sender.sendMessage("Player " + name + " could not be found");
+ } else {
+ sender.sendMessage("Player " + name + " has permission groups: "
+ + String.join(", ", permissionsManager.getGroups(player)));
+ sender.sendMessage("Primary group is: " + permissionsManager.getGroups(player));
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java
new file mode 100644
index 000000000..a985c8271
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PlayerAuthViewer.java
@@ -0,0 +1,104 @@
+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.security.crypts.HashedPassword;
+import fr.xephi.authme.util.StringUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
+
+/**
+ * Allows to view the data of a PlayerAuth in the database.
+ */
+class PlayerAuthViewer implements DebugSection {
+
+ @Inject
+ private DataSource dataSource;
+
+ @Override
+ public String getName() {
+ return "db";
+ }
+
+ @Override
+ public String getDescription() {
+ return "View player's data in the database";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.isEmpty()) {
+ sender.sendMessage("Enter player name to view his data in the database.");
+ sender.sendMessage("Example: /authme debug db Bobby");
+ return;
+ }
+
+ PlayerAuth auth = dataSource.getAuth(arguments.get(0));
+ if (auth == null) {
+ sender.sendMessage("No record exists for '" + arguments.get(0) + "'");
+ } else {
+ displayAuthToSender(auth, sender);
+ }
+ }
+
+ /**
+ * Outputs the PlayerAuth information to the given sender.
+ *
+ * @param auth the PlayerAuth to display
+ * @param sender the sender to send the messages to
+ */
+ private void displayAuthToSender(PlayerAuth auth, CommandSender sender) {
+ sender.sendMessage(ChatColor.GOLD + "[AuthMe] Player " + auth.getNickname() + " / " + auth.getRealName());
+ sender.sendMessage("Email: " + auth.getEmail() + ". IP: " + auth.getIp() + ". Group: " + auth.getGroupId());
+ sender.sendMessage("Quit location: "
+ + formatLocation(auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getWorld()));
+ sender.sendMessage("Last login: " + formatLastLogin(auth));
+
+ HashedPassword hashedPass = auth.getPassword();
+ sender.sendMessage("Hash / salt (partial): '" + safeSubstring(hashedPass.getHash(), 6)
+ + "' / '" + safeSubstring(hashedPass.getSalt(), 4) + "'");
+ }
+
+ /**
+ * Fail-safe substring method. Guarantees not to show the entire String.
+ *
+ * @param str the string to transform
+ * @param length number of characters to show from the start of the String
+ * @return the first length
characters of the string, or half of the string if it is shorter,
+ * or empty string if the string is null or empty
+ */
+ private static String safeSubstring(String str, int length) {
+ if (StringUtils.isEmpty(str)) {
+ return "";
+ } else if (str.length() < length) {
+ return str.substring(0, str.length() / 2) + "...";
+ } else {
+ return str.substring(0, length) + "...";
+ }
+ }
+
+ /**
+ * Formats the last login date from the given PlayerAuth.
+ *
+ * @param auth the auth object
+ * @return the last login as human readable date
+ */
+ private static String formatLastLogin(PlayerAuth auth) {
+ long lastLogin = auth.getLastLogin();
+ if (lastLogin == 0) {
+ return "Never (0)";
+ } else {
+ LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastLogin), ZoneId.systemDefault());
+ return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date);
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java
new file mode 100644
index 000000000..0262055e3
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/SpawnLocationViewer.java
@@ -0,0 +1,78 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.SpawnLoader;
+import fr.xephi.authme.settings.properties.RestrictionSettings;
+import org.bukkit.Location;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.List;
+
+import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
+
+/**
+ * Shows the spawn location that AuthMe is configured to use.
+ */
+class SpawnLocationViewer implements DebugSection {
+
+ @Inject
+ private SpawnLoader spawnLoader;
+
+ @Inject
+ private Settings settings;
+
+ @Inject
+ private BukkitService bukkitService;
+
+
+ @Override
+ public String getName() {
+ return "spawn";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Shows the spawn location that AuthMe will use";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (arguments.isEmpty()) {
+ showGeneralInfo(sender);
+ } else if ("?".equals(arguments.get(0))) {
+ showHelp(sender);
+ } else {
+ showPlayerSpawn(sender, arguments.get(0));
+ }
+ }
+
+ private void showGeneralInfo(CommandSender sender) {
+ sender.sendMessage("Spawn priority: "
+ + String.join(", ", settings.getProperty(RestrictionSettings.SPAWN_PRIORITY)));
+ sender.sendMessage("AuthMe spawn location: " + formatLocation(spawnLoader.getSpawn()));
+ sender.sendMessage("AuthMe first spawn location: " + formatLocation(spawnLoader.getFirstSpawn()));
+ sender.sendMessage("AuthMe (first)spawn are only used depending on the configured priority!");
+ sender.sendMessage("Use '/authme debug spawn ?' for further help");
+ }
+
+ private void showHelp(CommandSender sender) {
+ sender.sendMessage("Use /authme spawn and /authme firstspawn to teleport to the spawns.");
+ sender.sendMessage("/authme set(first)spawn sets the (first) spawn to your current location.");
+ sender.sendMessage("Use /authme debug spawn to view where a player would be teleported to.");
+ sender.sendMessage("Read more at https://github.com/AuthMe/AuthMeReloaded/wiki/Spawn-Handling");
+ }
+
+ private void showPlayerSpawn(CommandSender sender, String playerName) {
+ Player player = bukkitService.getPlayerExact(playerName);
+ if (player == null) {
+ sender.sendMessage("Player '" + playerName + "' is not online!");
+ } else {
+ Location spawn = spawnLoader.getSpawnLocation(player);
+ sender.sendMessage("Player '" + playerName + "' has spawn location: " + formatLocation(spawn));
+ sender.sendMessage("Note: this check excludes the AuthMe firstspawn.");
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java
new file mode 100644
index 000000000..04ba276a6
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java
@@ -0,0 +1,102 @@
+package fr.xephi.authme.command.executable.authme.debug;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.mail.SendMailSsl;
+import fr.xephi.authme.util.StringUtils;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.bukkit.ChatColor;
+import org.bukkit.Server;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Sends out a test email.
+ */
+class TestEmailSender implements DebugSection {
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private SendMailSsl sendMailSsl;
+
+ @Inject
+ private Server server;
+
+
+ @Override
+ public String getName() {
+ return "mail";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sends out a test email";
+ }
+
+ @Override
+ public void execute(CommandSender sender, List arguments) {
+ if (!sendMailSsl.hasAllInformation()) {
+ sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml "
+ + "for sending emails. Please check your config.yml");
+ return;
+ }
+
+ String email = getEmail(sender, arguments);
+
+ // getEmail() takes care of informing the sender of the error if email == null
+ if (email != null) {
+ boolean sendMail = sendTestEmail(email);
+ if (sendMail) {
+ sender.sendMessage("Test email sent to " + email + " with success");
+ } else {
+ sender.sendMessage(ChatColor.RED + "Failed to send test mail to " + email + "; please check your logs");
+ }
+ }
+ }
+
+ private String getEmail(CommandSender sender, List arguments) {
+ if (arguments.isEmpty()) {
+ PlayerAuth auth = dataSource.getAuth(sender.getName());
+ if (auth == null) {
+ sender.sendMessage(ChatColor.RED + "Please provide an email address, "
+ + "e.g. /authme debug mail test@example.com");
+ return null;
+ }
+ String email = auth.getEmail();
+ if (email == null || "your@email.com".equals(email)) {
+ sender.sendMessage(ChatColor.RED + "No email set for your account!"
+ + " Please use /authme debug mail ");
+ return null;
+ }
+ return email;
+ } else {
+ String email = arguments.get(0);
+ if (StringUtils.isInsideString('@', email)) {
+ return email;
+ }
+ sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com");
+ return null;
+ }
+ }
+
+ private boolean sendTestEmail(String email) {
+ HtmlEmail htmlEmail;
+ try {
+ htmlEmail = sendMailSsl.initializeMail(email);
+ } catch (EmailException e) {
+ ConsoleLogger.logException("Failed to create email for sample email:", e);
+ return false;
+ }
+
+ htmlEmail.setSubject("AuthMe test email");
+ String message = "Hello there! This is a sample email sent to you from a Minecraft server ("
+ + server.getName() + ") via /authme debug mail. If you're seeing this, sending emails should be fine.";
+ return sendMailSsl.sendEmail(message, htmlEmail);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java
index c5756cda9..259e20f96 100644
--- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java
@@ -3,8 +3,7 @@ package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.CaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.command.PlayerCommand;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.entity.Player;
@@ -24,7 +23,7 @@ public class CaptchaCommand extends PlayerCommand {
private CommonService commonService;
@Inject
- private LimboCache limboCache;
+ private LimboService limboService;
@Override
public void runCommand(Player player, List arguments) {
@@ -44,7 +43,7 @@ public class CaptchaCommand extends PlayerCommand {
if (isCorrectCode) {
commonService.send(player, MessageKey.CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.LOGIN_MESSAGE);
- limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(false);
+ limboService.unmuteMessageTask(player);
} else {
String newCode = captchaManager.generateCode(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
diff --git a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java
index 7c5e7d9dc..7c4b53c35 100644
--- a/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/changepassword/ChangePasswordCommand.java
@@ -54,4 +54,9 @@ public class ChangePasswordCommand extends PlayerCommand {
protected String getAlternativeCommand() {
return "/authme password ";
}
+
+ @Override
+ public MessageKey getArgumentsMismatchMessage() {
+ return MessageKey.USAGE_CHANGE_PASSWORD;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java
index eae64fdb0..87e381047 100644
--- a/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/email/AddEmailCommand.java
@@ -32,4 +32,9 @@ public class AddEmailCommand extends PlayerCommand {
commonService.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE);
}
}
+
+ @Override
+ public MessageKey getArgumentsMismatchMessage() {
+ return MessageKey.USAGE_ADD_EMAIL;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java
index 1f9051e38..aabe99bc3 100644
--- a/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/email/ChangeEmailCommand.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
+import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import org.bukkit.entity.Player;
@@ -22,4 +23,9 @@ public class ChangeEmailCommand extends PlayerCommand {
management.performChangeEmail(player, playerMailOld, playerMailNew);
}
+
+ @Override
+ public MessageKey getArgumentsMismatchMessage() {
+ return MessageKey.USAGE_CHANGE_EMAIL;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java
new file mode 100644
index 000000000..0883c18f9
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/email/ProcessCodeCommand.java
@@ -0,0 +1,46 @@
+package fr.xephi.authme.command.executable.email;
+
+import fr.xephi.authme.command.PlayerCommand;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.PasswordRecoveryService;
+import fr.xephi.authme.service.RecoveryCodeService;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Command for submitting email recovery code.
+ */
+public class ProcessCodeCommand extends PlayerCommand {
+
+ @Inject
+ private CommonService commonService;
+
+ @Inject
+ private RecoveryCodeService codeService;
+
+ @Inject
+ private PasswordRecoveryService recoveryService;
+
+ @Override
+ protected void runCommand(Player player, List arguments) {
+ String name = player.getName();
+ String code = arguments.get(0);
+
+ if (codeService.hasTriesLeft(name)) {
+ if (codeService.isCodeValid(name, code)) {
+ commonService.send(player, MessageKey.RECOVERY_CODE_CORRECT);
+ recoveryService.addSuccessfulRecovery(player);
+ codeService.removeCode(name);
+ } else {
+ commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE,
+ Integer.toString(codeService.getTriesLeft(name)));
+ }
+ } else {
+ codeService.removeCode(name);
+ commonService.send(player, MessageKey.RECOVERY_TRIES_EXCEEDED);
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
index 5dc16ac60..bd50cc743 100644
--- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java
@@ -5,28 +5,21 @@ import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
-import fr.xephi.authme.mail.SendMailSSL;
+import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
-import fr.xephi.authme.security.PasswordSecurity;
-import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.RecoveryCodeService;
-import fr.xephi.authme.util.RandomStringUtils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
-import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
-
/**
* Command for password recovery by email.
*/
public class RecoverEmailCommand extends PlayerCommand {
- @Inject
- private PasswordSecurity passwordSecurity;
-
@Inject
private CommonService commonService;
@@ -37,17 +30,20 @@ public class RecoverEmailCommand extends PlayerCommand {
private PlayerCache playerCache;
@Inject
- private SendMailSSL sendMailSsl;
+ private EmailService emailService;
+
+ @Inject
+ private PasswordRecoveryService recoveryService;
@Inject
private RecoveryCodeService recoveryCodeService;
@Override
- public void runCommand(Player player, List arguments) {
+ protected void runCommand(Player player, List arguments) {
final String playerMail = arguments.get(0);
final String playerName = player.getName();
- if (!sendMailSsl.hasAllInformation()) {
+ if (!emailService.hasAllInformation()) {
ConsoleLogger.warning("Mail API is not set");
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
return;
@@ -57,7 +53,7 @@ public class RecoverEmailCommand extends PlayerCommand {
return;
}
- PlayerAuth auth = dataSource.getAuth(playerName); // TODO: Create method to get email only
+ PlayerAuth auth = dataSource.getAuth(playerName); // TODO #1127: Create method to get email only
if (auth == null) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return;
@@ -70,49 +66,16 @@ public class RecoverEmailCommand extends PlayerCommand {
}
if (recoveryCodeService.isRecoveryCodeNeeded()) {
- // Process /email recovery addr@example.com
- if (arguments.size() == 1) {
- createAndSendRecoveryCode(player, email);
- } else {
- // Process /email recovery addr@example.com 12394
- processRecoveryCode(player, arguments.get(1), email);
- }
+ // Recovery code is needed; generate and send one
+ recoveryService.createAndSendRecoveryCode(player, email);
} else {
- generateAndSendNewPassword(player, email);
+ // Code not needed, just send them a new password
+ recoveryService.generateAndSendNewPassword(player, email);
}
}
- private void createAndSendRecoveryCode(Player player, String email) {
- String recoveryCode = recoveryCodeService.generateCode(player.getName());
- boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode);
- if (couldSendMail) {
- commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
- } else {
- commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
- }
- }
-
- private void processRecoveryCode(Player player, String code, String email) {
- final String name = player.getName();
- if (recoveryCodeService.isCodeValid(name, code)) {
- generateAndSendNewPassword(player, email);
- recoveryCodeService.removeCode(name);
- } else {
- commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE);
- }
- }
-
- private void generateAndSendNewPassword(Player player, String email) {
- String name = player.getName();
- String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
- HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
-
- dataSource.updatePassword(name, hashNew);
- boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass);
- if (couldSendMail) {
- commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
- } else {
- commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
- }
+ @Override
+ public MessageKey getArgumentsMismatchMessage() {
+ return MessageKey.USAGE_RECOVER_EMAIL;
}
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java
new file mode 100644
index 000000000..d5d084aa6
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/email/SetPasswordCommand.java
@@ -0,0 +1,55 @@
+package fr.xephi.authme.command.executable.email;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.command.PlayerCommand;
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.PasswordRecoveryService;
+import fr.xephi.authme.service.ValidationService;
+import fr.xephi.authme.service.ValidationService.ValidationResult;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Command for changing password following successful recovery.
+ */
+public class SetPasswordCommand extends PlayerCommand {
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private CommonService commonService;
+
+ @Inject
+ private PasswordRecoveryService recoveryService;
+
+ @Inject
+ private PasswordSecurity passwordSecurity;
+
+ @Inject
+ private ValidationService validationService;
+
+ @Override
+ protected void runCommand(Player player, List arguments) {
+ if (recoveryService.canChangePassword(player)) {
+ String name = player.getName();
+ String password = arguments.get(0);
+
+ ValidationResult result = validationService.validatePassword(password, name);
+ if (!result.hasError()) {
+ HashedPassword hashedPassword = passwordSecurity.computeHash(password, name);
+ dataSource.updatePassword(name, hashedPassword);
+ ConsoleLogger.info("Player '" + name + "' has changed their password from recovery");
+ commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
+ } else {
+ commonService.send(player, result.getMessageKey(), result.getArgs());
+ }
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java
index 151236e1a..24a300ef8 100644
--- a/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/email/ShowEmailCommand.java
@@ -24,7 +24,7 @@ public class ShowEmailCommand extends PlayerCommand {
@Override
public void runCommand(Player player, List arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
- if (auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) {
+ if (auth != null && auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) {
commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail());
} else {
commonService.send(player, MessageKey.SHOW_NO_EMAIL);
diff --git a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java
index ac4bb4bd2..5b9d75ab1 100644
--- a/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/register/RegisterCommand.java
@@ -2,12 +2,15 @@ package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
-import fr.xephi.authme.mail.SendMailSSL;
+import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
-import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
+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.TwoFactorRegisterParams;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
@@ -37,20 +40,17 @@ public class RegisterCommand extends PlayerCommand {
private CommonService commonService;
@Inject
- private SendMailSSL sendMailSsl;
+ private EmailService emailService;
@Inject
private ValidationService validationService;
- @Inject
- private RegistrationExecutorProvider registrationExecutorProvider;
-
@Override
public void runCommand(Player player, List arguments) {
if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) {
//for two factor auth we don't need to check the usage
- management.performRegister(player,
- registrationExecutorProvider.getTwoFactorRegisterExecutor(player));
+ management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION,
+ TwoFactorRegisterParams.of(player));
return;
} else if (arguments.size() < 1) {
commonService.send(player, MessageKey.USAGE_REGISTER);
@@ -82,8 +82,8 @@ public class RegisterCommand extends PlayerCommand {
final String password = arguments.get(0);
final String email = getEmailIfAvailable(arguments);
- management.performRegister(
- player, registrationExecutorProvider.getPasswordRegisterExecutor(player, password, email));
+ management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
+ PasswordRegisterParams.of(player, password, email));
}
}
@@ -127,7 +127,7 @@ public class RegisterCommand extends PlayerCommand {
}
private void handleEmailRegistration(Player player, List arguments) {
- if (!sendMailSsl.hasAllInformation()) {
+ if (!emailService.hasAllInformation()) {
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
ConsoleLogger.warning("Cannot register player '" + player.getName() + "': no email or password is set "
+ "to send emails from. Please adjust your config at " + EmailSettings.MAIL_ACCOUNT.getPath());
@@ -138,7 +138,8 @@ public class RegisterCommand extends PlayerCommand {
if (!validationService.validateEmail(email)) {
commonService.send(player, MessageKey.INVALID_EMAIL);
} else if (isSecondArgValidForEmailRegistration(player, arguments)) {
- management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email));
+ management.performRegister(RegistrationMethod.EMAIL_REGISTRATION,
+ EmailRegisterParams.of(player, email));
}
}
diff --git a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java
index 09b996a27..dd747e0e4 100644
--- a/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java
+++ b/src/main/java/fr/xephi/authme/command/executable/unregister/UnregisterCommand.java
@@ -38,4 +38,14 @@ public class UnregisterCommand extends PlayerCommand {
// Unregister the player
management.performUnregister(player, playerPass);
}
+
+ @Override
+ public MessageKey getArgumentsMismatchMessage() {
+ return MessageKey.USAGE_UNREGISTER;
+ }
+
+ @Override
+ protected String getAlternativeCommand() {
+ return "/authme unregister ";
+ }
}
diff --git a/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java b/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java
index c2cbaff99..7058c0cb8 100644
--- a/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java
+++ b/src/main/java/fr/xephi/authme/command/help/HelpMessagesService.java
@@ -81,8 +81,8 @@ public class HelpMessagesService implements Reloadable {
public String getMessage(DefaultPermission defaultPermission) {
// e.g. {default_permissions_path}.opOnly for DefaultPermission.OP_ONLY
- String path = DEFAULT_PERMISSIONS_PATH +
- CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
+ String path = DEFAULT_PERMISSIONS_PATH
+ + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
return messageFileHandler.getMessage(path);
}
diff --git a/src/main/java/fr/xephi/authme/data/CaptchaManager.java b/src/main/java/fr/xephi/authme/data/CaptchaManager.java
index 36f33a3cb..b328d5450 100644
--- a/src/main/java/fr/xephi/authme/data/CaptchaManager.java
+++ b/src/main/java/fr/xephi/authme/data/CaptchaManager.java
@@ -1,19 +1,22 @@
package fr.xephi.authme.data;
+import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
-import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
+import fr.xephi.authme.util.RandomStringUtils;
+import fr.xephi.authme.util.expiring.TimedCounter;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
/**
* Manager for the handling of captchas.
*/
-public class CaptchaManager implements SettingsDependent {
+public class CaptchaManager implements SettingsDependent, HasCleanup {
- private final ConcurrentHashMap playerCounts;
+ private final TimedCounter playerCounts;
private final ConcurrentHashMap captchaCodes;
private boolean isEnabled;
@@ -22,8 +25,9 @@ public class CaptchaManager implements SettingsDependent {
@Inject
CaptchaManager(Settings settings) {
- this.playerCounts = new ConcurrentHashMap<>();
this.captchaCodes = new ConcurrentHashMap<>();
+ long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);
+ this.playerCounts = new TimedCounter<>(countTimeout, TimeUnit.MINUTES);
reload(settings);
}
@@ -35,12 +39,7 @@ public class CaptchaManager implements SettingsDependent {
public void increaseCount(String name) {
if (isEnabled) {
String playerLower = name.toLowerCase();
- Integer currentCount = playerCounts.get(playerLower);
- if (currentCount == null) {
- playerCounts.put(playerLower, 1);
- } else {
- playerCounts.put(playerLower, currentCount + 1);
- }
+ playerCounts.increment(playerLower);
}
}
@@ -51,21 +50,7 @@ public class CaptchaManager implements SettingsDependent {
* @return true if the player has to solve a captcha, false otherwise
*/
public boolean isCaptchaRequired(String name) {
- if (isEnabled) {
- Integer count = playerCounts.get(name.toLowerCase());
- return count != null && count >= threshold;
- }
- return false;
- }
-
- /**
- * Returns the stored captcha code for the player.
- *
- * @param name the player's name
- * @return the code the player is required to enter, or null if none registered
- */
- public String getCaptchaCode(String name) {
- return captchaCodes.get(name.toLowerCase());
+ return isEnabled && playerCounts.get(name.toLowerCase()) >= threshold;
}
/**
@@ -75,7 +60,7 @@ public class CaptchaManager implements SettingsDependent {
* @return the code the player is required to enter
*/
public String getCaptchaCodeOrGenerateNew(String name) {
- String code = getCaptchaCode(name);
+ String code = captchaCodes.get(name.toLowerCase());
return code == null ? generateCode(name) : code;
}
@@ -127,6 +112,13 @@ public class CaptchaManager implements SettingsDependent {
this.isEnabled = settings.getProperty(SecuritySettings.USE_CAPTCHA);
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA);
this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH);
+ long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);
+ playerCounts.setExpiration(countTimeout, TimeUnit.MINUTES);
+ }
+
+ @Override
+ public void performCleanup() {
+ playerCounts.removeExpiredEntries();
}
}
diff --git a/src/main/java/fr/xephi/authme/data/SessionManager.java b/src/main/java/fr/xephi/authme/data/SessionManager.java
index f86f54214..512941db6 100644
--- a/src/main/java/fr/xephi/authme/data/SessionManager.java
+++ b/src/main/java/fr/xephi/authme/data/SessionManager.java
@@ -5,13 +5,10 @@ import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
+import fr.xephi.authme.util.expiring.ExpiringSet;
import javax.inject.Inject;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
+import java.util.concurrent.TimeUnit;
/**
* Manages sessions, allowing players to be automatically logged in if they join again
@@ -19,15 +16,14 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
*/
public class SessionManager implements SettingsDependent, HasCleanup {
- // Player -> expiration of session in milliseconds
- private final Map sessions = new ConcurrentHashMap<>();
-
+ private final ExpiringSet sessions;
private boolean enabled;
- private int timeoutInMinutes;
@Inject
SessionManager(Settings settings) {
- reload(settings);
+ long timeout = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
+ sessions = new ExpiringSet<>(timeout, TimeUnit.MINUTES);
+ enabled = timeout > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED);
}
/**
@@ -37,13 +33,7 @@ public class SessionManager implements SettingsDependent, HasCleanup {
* @return True if a session is found.
*/
public boolean hasSession(String name) {
- if (enabled) {
- Long timeout = sessions.get(name.toLowerCase());
- if (timeout != null) {
- return System.currentTimeMillis() <= timeout;
- }
- }
- return false;
+ return enabled && sessions.contains(name.toLowerCase());
}
/**
@@ -53,8 +43,7 @@ public class SessionManager implements SettingsDependent, HasCleanup {
*/
public void addSession(String name) {
if (enabled) {
- long timeout = System.currentTimeMillis() + timeoutInMinutes * MILLIS_PER_MINUTE;
- sessions.put(name.toLowerCase(), timeout);
+ sessions.add(name.toLowerCase());
}
}
@@ -64,12 +53,13 @@ public class SessionManager implements SettingsDependent, HasCleanup {
* @param name The name of the player.
*/
public void removeSession(String name) {
- this.sessions.remove(name.toLowerCase());
+ sessions.remove(name.toLowerCase());
}
@Override
public void reload(Settings settings) {
- timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
+ long timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
+ sessions.setExpiration(timeoutInMinutes, TimeUnit.MINUTES);
boolean oldEnabled = enabled;
enabled = timeoutInMinutes > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED);
@@ -82,16 +72,8 @@ public class SessionManager implements SettingsDependent, HasCleanup {
@Override
public void performCleanup() {
- if (!enabled) {
- return;
- }
- final long currentTime = System.currentTimeMillis();
- Iterator> iterator = sessions.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry entry = iterator.next();
- if (entry.getValue() < currentTime) {
- iterator.remove();
- }
+ if (enabled) {
+ sessions.removeExpiredEntries();
}
}
}
diff --git a/src/main/java/fr/xephi/authme/data/TempbanManager.java b/src/main/java/fr/xephi/authme/data/TempbanManager.java
index e5d31ed1a..1d55fa8f5 100644
--- a/src/main/java/fr/xephi/authme/data/TempbanManager.java
+++ b/src/main/java/fr/xephi/authme/data/TempbanManager.java
@@ -1,21 +1,21 @@
package fr.xephi.authme.data;
-import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
+import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
-import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
+import fr.xephi.authme.util.expiring.TimedCounter;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.Date;
-import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.SecuritySettings.TEMPBAN_MINUTES_BEFORE_RESET;
import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
@@ -25,7 +25,7 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
*/
public class TempbanManager implements SettingsDependent, HasCleanup {
- private final Map> ipLoginFailureCounts;
+ private final Map> ipLoginFailureCounts;
private final BukkitService bukkitService;
private final Messages messages;
@@ -50,18 +50,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public void increaseCount(String address, String name) {
if (isEnabled) {
- Map countsByName = ipLoginFailureCounts.get(address);
- if (countsByName == null) {
- countsByName = new ConcurrentHashMap<>();
- ipLoginFailureCounts.put(address, countsByName);
- }
-
- TimedCounter counter = countsByName.get(name);
- if (counter == null) {
- countsByName.put(name, new TimedCounter(1));
- } else {
- counter.increment(resetThreshold);
- }
+ TimedCounter countsByName = ipLoginFailureCounts.computeIfAbsent(
+ address, k -> new TimedCounter<>(resetThreshold, TimeUnit.MINUTES));
+ countsByName.increment(name);
}
}
@@ -73,9 +64,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public void resetCount(String address, String name) {
if (isEnabled) {
- Map map = ipLoginFailureCounts.get(address);
- if (map != null) {
- map.remove(name);
+ TimedCounter counter = ipLoginFailureCounts.get(address);
+ if (counter != null) {
+ counter.remove(name);
}
}
}
@@ -88,13 +79,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public boolean shouldTempban(String address) {
if (isEnabled) {
- Map countsByName = ipLoginFailureCounts.get(address);
+ TimedCounter countsByName = ipLoginFailureCounts.get(address);
if (countsByName != null) {
- int total = 0;
- for (TimedCounter counter : countsByName.values()) {
- total += counter.getCount(resetThreshold);
- }
- return total >= threshold;
+ return countsByName.total() >= threshold;
}
}
return false;
@@ -137,56 +124,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
@Override
public void performCleanup() {
- for (Map countsByIp : ipLoginFailureCounts.values()) {
- Iterator it = countsByIp.values().iterator();
- while (it.hasNext()) {
- TimedCounter counter = it.next();
- if (counter.getCount(resetThreshold) == 0) {
- it.remove();
- }
- }
- }
- }
-
- /**
- * Counter with an associated timestamp, keeping track of when the last entry has been added.
- */
- @VisibleForTesting
- static final class TimedCounter {
-
- private int counter;
- private long lastIncrementTimestamp = System.currentTimeMillis();
-
- /**
- * Constructor.
- *
- * @param start the initial value to set the counter to
- */
- TimedCounter(int start) {
- this.counter = start;
- }
-
- /**
- * Returns the count, taking into account the last entry timestamp.
- *
- * @param threshold the threshold in milliseconds until when to consider a counter
- * @return the counter's value, or {@code 0} if it was last incremented longer ago than the threshold
- */
- int getCount(long threshold) {
- if (System.currentTimeMillis() - lastIncrementTimestamp > threshold) {
- return 0;
- }
- return counter;
- }
-
- /**
- * Increments the counter, taking into account the last entry timestamp.
- *
- * @param threshold in milliseconds, the time span until which to consider the existing number
- */
- void increment(long threshold) {
- counter = getCount(threshold) + 1;
- lastIncrementTimestamp = System.currentTimeMillis();
+ for (TimedCounter countsByIp : ipLoginFailureCounts.values()) {
+ countsByIp.removeExpiredEntries();
}
+ ipLoginFailureCounts.entrySet().removeIf(e -> e.getValue().isEmpty());
}
}
diff --git a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java
deleted file mode 100644
index 1b2270285..000000000
--- a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package fr.xephi.authme.data.backup;
-
-import com.google.common.io.Files;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.data.limbo.LimboPlayer;
-import fr.xephi.authme.initialization.DataFolder;
-import fr.xephi.authme.permission.PermissionsManager;
-import fr.xephi.authme.settings.SpawnLoader;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.util.FileUtils;
-import fr.xephi.authme.util.PlayerUtils;
-import org.bukkit.Location;
-import org.bukkit.World;
-import org.bukkit.entity.Player;
-
-import javax.inject.Inject;
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
-
-/**
- * Class used to store player's data (OP, flying, speed, position) to disk.
- */
-public class LimboPlayerStorage {
-
- private final Gson gson;
- private final File cacheDir;
- private PermissionsManager permissionsManager;
- private SpawnLoader spawnLoader;
- private BukkitService bukkitService;
-
- @Inject
- LimboPlayerStorage(@DataFolder File dataFolder, PermissionsManager permsMan,
- SpawnLoader spawnLoader, BukkitService bukkitService) {
- this.permissionsManager = permsMan;
- this.spawnLoader = spawnLoader;
- this.bukkitService = bukkitService;
-
- cacheDir = new File(dataFolder, "playerdata");
- if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
- ConsoleLogger.warning("Failed to create userdata directory.");
- }
- gson = new GsonBuilder()
- .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
- .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer())
- .setPrettyPrinting()
- .create();
- }
-
- /**
- * Read and construct new PlayerData from existing player data.
- *
- * @param player player to read
- *
- * @return PlayerData object if the data is exist, null otherwise.
- */
- public LimboPlayer readData(Player player) {
- String id = PlayerUtils.getUUIDorName(player);
- File file = new File(cacheDir, id + File.separator + "data.json");
- if (!file.exists()) {
- return null;
- }
-
- try {
- String str = Files.toString(file, StandardCharsets.UTF_8);
- return gson.fromJson(str, LimboPlayer.class);
- } catch (IOException e) {
- ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
- return null;
- }
- }
-
- /**
- * Save player data (OP, flying, location, etc) to disk.
- *
- * @param player player to save
- */
- public void saveData(Player player) {
- String id = PlayerUtils.getUUIDorName(player);
- Location location = spawnLoader.getPlayerLocationOrSpawn(player);
- String group = "";
- if (permissionsManager.hasGroupSupport()) {
- group = permissionsManager.getPrimaryGroup(player);
- }
- boolean operator = player.isOp();
- boolean canFly = player.getAllowFlight();
- float walkSpeed = player.getWalkSpeed();
- float flySpeed = player.getFlySpeed();
- LimboPlayer limboPlayer = new LimboPlayer(location, operator, group, canFly, walkSpeed, flySpeed);
- try {
- File file = new File(cacheDir, id + File.separator + "data.json");
- Files.createParentDirs(file);
- Files.touch(file);
- Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8);
- } catch (IOException e) {
- ConsoleLogger.logException("Failed to write " + player.getName() + " data.", e);
- }
- }
-
- /**
- * Remove player data, this will delete
- * "playerdata/<uuid or name>/" folder from disk.
- *
- * @param player player to remove
- */
- public void removeData(Player player) {
- String id = PlayerUtils.getUUIDorName(player);
- File file = new File(cacheDir, id);
- if (file.exists()) {
- FileUtils.purgeDirectory(file);
- if (!file.delete()) {
- ConsoleLogger.warning("Failed to remove " + player.getName() + " cache.");
- }
- }
- }
-
- /**
- * Use to check is player data is exist.
- *
- * @param player player to check
- *
- * @return true if data exist, false otherwise.
- */
- public boolean hasData(Player player) {
- String id = PlayerUtils.getUUIDorName(player);
- File file = new File(cacheDir, id + File.separator + "data.json");
- return file.exists();
- }
-
- private class LimboPlayerDeserializer implements JsonDeserializer {
- @Override
- public LimboPlayer deserialize(JsonElement jsonElement, Type type,
- JsonDeserializationContext context) {
- JsonObject jsonObject = jsonElement.getAsJsonObject();
- if (jsonObject == null) {
- return null;
- }
-
- Location loc = null;
- String group = "";
- boolean operator = false;
- boolean canFly = false;
- float walkSpeed = 0.2f;
- float flySpeed = 0.2f;
-
- JsonElement e;
- if ((e = jsonObject.getAsJsonObject("location")) != null) {
- JsonObject obj = e.getAsJsonObject();
- World world = bukkitService.getWorld(obj.get("world").getAsString());
- if (world != null) {
- double x = obj.get("x").getAsDouble();
- double y = obj.get("y").getAsDouble();
- double z = obj.get("z").getAsDouble();
- float yaw = obj.get("yaw").getAsFloat();
- float pitch = obj.get("pitch").getAsFloat();
- loc = new Location(world, x, y, z, yaw, pitch);
- }
- }
- if ((e = jsonObject.get("group")) != null) {
- group = e.getAsString();
- }
- if ((e = jsonObject.get("operator")) != null) {
- operator = e.getAsBoolean();
- }
- if ((e = jsonObject.get("can-fly")) != null) {
- canFly = e.getAsBoolean();
- }
- if ((e = jsonObject.get("walk-speed")) != null) {
- walkSpeed = e.getAsFloat();
- }
- if ((e = jsonObject.get("fly-speed")) != null) {
- flySpeed = e.getAsFloat();
- }
-
- return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
- }
- }
-
- private class LimboPlayerSerializer implements JsonSerializer {
- @Override
- public JsonElement serialize(LimboPlayer limboPlayer, Type type,
- JsonSerializationContext context) {
- JsonObject obj = new JsonObject();
- obj.addProperty("group", limboPlayer.getGroup());
-
- Location loc = limboPlayer.getLocation();
- JsonObject obj2 = new JsonObject();
- obj2.addProperty("world", loc.getWorld().getName());
- obj2.addProperty("x", loc.getX());
- obj2.addProperty("y", loc.getY());
- obj2.addProperty("z", loc.getZ());
- obj2.addProperty("yaw", loc.getYaw());
- obj2.addProperty("pitch", loc.getPitch());
- obj.add("location", obj2);
-
- obj.addProperty("operator", limboPlayer.isOperator());
- obj.addProperty("can-fly", limboPlayer.isCanFly());
- obj.addProperty("walk-speed", limboPlayer.getWalkSpeed());
- obj.addProperty("fly-speed", limboPlayer.getFlySpeed());
- return obj;
- }
- }
-
-
-}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java
new file mode 100644
index 000000000..f085afbb4
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/AllowFlightRestoreType.java
@@ -0,0 +1,43 @@
+package fr.xephi.authme.data.limbo;
+
+import org.bukkit.entity.Player;
+
+import java.util.function.Function;
+
+/**
+ * Possible types to restore the "allow flight" property
+ * from LimboPlayer to Bukkit Player.
+ */
+public enum AllowFlightRestoreType {
+
+ /** Set value from LimboPlayer to Player. */
+ RESTORE(LimboPlayer::isCanFly),
+
+ /** Always set flight enabled to true. */
+ ENABLE(l -> true),
+
+ /** Always set flight enabled to false. */
+ DISABLE(l -> false);
+
+ private final Function valueGetter;
+
+ /**
+ * Constructor.
+ *
+ * @param valueGetter function with which the value to set on the player can be retrieved
+ */
+ AllowFlightRestoreType(Function valueGetter) {
+ this.valueGetter = valueGetter;
+ }
+
+ /**
+ * Restores the "allow flight" property from the LimboPlayer to the Player.
+ * This method behaves differently for each restoration type.
+ *
+ * @param player the player to modify
+ * @param limbo the limbo player to read from
+ */
+ public void restoreAllowFlight(Player player, LimboPlayer limbo) {
+ player.setAllowFlight(valueGetter.apply(limbo));
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java
deleted file mode 100644
index 893aba229..000000000
--- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package fr.xephi.authme.data.limbo;
-
-import fr.xephi.authme.data.backup.LimboPlayerStorage;
-import fr.xephi.authme.permission.PermissionsManager;
-import fr.xephi.authme.settings.Settings;
-import fr.xephi.authme.settings.SpawnLoader;
-import fr.xephi.authme.settings.properties.PluginSettings;
-import fr.xephi.authme.util.StringUtils;
-import org.bukkit.Location;
-import org.bukkit.entity.Player;
-
-import javax.inject.Inject;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Manages all {@link LimboPlayer} instances.
- */
-public class LimboCache {
-
- private final Map cache = new ConcurrentHashMap<>();
-
- private LimboPlayerStorage limboPlayerStorage;
- private Settings settings;
- private PermissionsManager permissionsManager;
- private SpawnLoader spawnLoader;
-
- @Inject
- LimboCache(Settings settings, PermissionsManager permissionsManager,
- SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) {
- this.settings = settings;
- this.permissionsManager = permissionsManager;
- this.spawnLoader = spawnLoader;
- this.limboPlayerStorage = limboPlayerStorage;
- }
-
- /**
- * Load player data if exist, otherwise current player's data will be stored.
- *
- * @param player Player instance to add.
- */
- public void addPlayerData(Player player) {
- String name = player.getName().toLowerCase();
- Location location = spawnLoader.getPlayerLocationOrSpawn(player);
- boolean operator = player.isOp();
- boolean flyEnabled = player.getAllowFlight();
- float walkSpeed = player.getWalkSpeed();
- float flySpeed = player.getFlySpeed();
- String playerGroup = "";
- if (permissionsManager.hasGroupSupport()) {
- playerGroup = permissionsManager.getPrimaryGroup(player);
- }
-
- if (limboPlayerStorage.hasData(player)) {
- LimboPlayer cache = limboPlayerStorage.readData(player);
- if (cache != null) {
- location = cache.getLocation();
- playerGroup = cache.getGroup();
- operator = cache.isOperator();
- flyEnabled = cache.isCanFly();
- walkSpeed = cache.getWalkSpeed();
- flySpeed = cache.getFlySpeed();
- }
- } else {
- limboPlayerStorage.saveData(player);
- }
-
- cache.put(name, new LimboPlayer(location, operator, playerGroup, flyEnabled, walkSpeed, flySpeed));
- }
-
- /**
- * Restore player's data to player if exist.
- *
- * @param player Player instance to restore
- */
- public void restoreData(Player player) {
- String lowerName = player.getName().toLowerCase();
- if (cache.containsKey(lowerName)) {
- LimboPlayer data = cache.get(lowerName);
- player.setOp(data.isOperator());
- player.setAllowFlight(data.isCanFly());
- float walkSpeed = data.getWalkSpeed();
- float flySpeed = data.getFlySpeed();
- // Reset the speed value if it was 0
- if(walkSpeed < 0.01f) {
- walkSpeed = 0.2f;
- }
- if(flySpeed < 0.01f) {
- flySpeed = 0.2f;
- }
- player.setWalkSpeed(walkSpeed);
- player.setFlySpeed(flySpeed);
- restoreGroup(player, data.getGroup());
- data.clearTasks();
- }
- }
-
- /**
- * Remove PlayerData from cache and disk.
- *
- * @param player Player player to remove.
- */
- public void deletePlayerData(Player player) {
- removeFromCache(player);
- limboPlayerStorage.removeData(player);
- }
-
- /**
- * Remove PlayerData from cache.
- *
- * @param player player to remove.
- */
- public void removeFromCache(Player player) {
- String name = player.getName().toLowerCase();
- LimboPlayer cachedPlayer = cache.remove(name);
- if (cachedPlayer != null) {
- cachedPlayer.clearTasks();
- }
- }
-
- /**
- * Method getPlayerData.
- *
- * @param name String
- *
- * @return PlayerData
- */
- public LimboPlayer getPlayerData(String name) {
- checkNotNull(name);
- return cache.get(name.toLowerCase());
- }
-
- /**
- * Method hasPlayerData.
- *
- * @param name String
- *
- * @return boolean
- */
- public boolean hasPlayerData(String name) {
- checkNotNull(name);
- return cache.containsKey(name.toLowerCase());
- }
-
- /**
- * Method updatePlayerData.
- *
- * @param player Player
- */
- public void updatePlayerData(Player player) {
- checkNotNull(player);
- removeFromCache(player);
- addPlayerData(player);
- }
-
- private void restoreGroup(Player player, String group) {
- if (!StringUtils.isEmpty(group) && permissionsManager.hasGroupSupport()
- && settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
- permissionsManager.setGroup(player, group);
- }
- }
-}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java
index b551deeda..6ba4ae2c5 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java
@@ -10,6 +10,9 @@ import org.bukkit.scheduler.BukkitTask;
*/
public class LimboPlayer {
+ public static final float DEFAULT_WALK_SPEED = 0.2f;
+ public static final float DEFAULT_FLY_SPEED = 0.1f;
+
private final boolean canFly;
private final boolean operator;
private final String group;
@@ -115,13 +118,7 @@ public class LimboPlayer {
* Clears all tasks associated to the player.
*/
public void clearTasks() {
- if (messageTask != null) {
- messageTask.cancel();
- }
- messageTask = null;
- if (timeoutTask != null) {
- timeoutTask.cancel();
- }
- timeoutTask = null;
+ setMessageTask(null);
+ setTimeoutTask(null);
}
}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java
new file mode 100644
index 000000000..4e0c0a336
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManager.java
@@ -0,0 +1,97 @@
+package fr.xephi.authme.data.limbo;
+
+import fr.xephi.authme.data.auth.PlayerCache;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.message.Messages;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.RegistrationSettings;
+import fr.xephi.authme.settings.properties.RestrictionSettings;
+import fr.xephi.authme.task.MessageTask;
+import fr.xephi.authme.task.TimeoutTask;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+
+import javax.inject.Inject;
+
+import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
+
+/**
+ * Registers tasks associated with a LimboPlayer.
+ */
+class LimboPlayerTaskManager {
+
+ @Inject
+ private Messages messages;
+
+ @Inject
+ private Settings settings;
+
+ @Inject
+ private BukkitService bukkitService;
+
+ @Inject
+ private PlayerCache playerCache;
+
+ LimboPlayerTaskManager() {
+ }
+
+ /**
+ * Registers a {@link MessageTask} for the given player name.
+ *
+ * @param player the player
+ * @param limbo the associated limbo player of the player
+ * @param isRegistered whether the player is registered or not
+ * (false shows "please register", true shows "please log in")
+ */
+ void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) {
+ int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
+ MessageKey key = getMessageKey(isRegistered);
+ if (interval > 0) {
+ MessageTask messageTask = new MessageTask(player, messages.retrieve(key));
+ bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND);
+ limbo.setMessageTask(messageTask);
+ }
+ }
+
+ /**
+ * Registers a {@link TimeoutTask} for the given player according to the configuration.
+ *
+ * @param player the player to register a timeout task for
+ * @param limbo the associated limbo player
+ */
+ void registerTimeoutTask(Player player, LimboPlayer limbo) {
+ final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
+ if (timeout > 0) {
+ String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR);
+ BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout);
+ limbo.setTimeoutTask(task);
+ }
+ }
+
+ /**
+ * Null-safe method to set the muted flag on a message task.
+ *
+ * @param task the task to modify (or null)
+ * @param isMuted the value to set if task is not null
+ */
+ static void setMuted(MessageTask task, boolean isMuted) {
+ if (task != null) {
+ task.setMuted(isMuted);
+ }
+ }
+
+ /**
+ * Returns the appropriate message key according to the registration status and settings.
+ *
+ * @param isRegistered whether or not the username is registered
+ * @return the message key to display to the user
+ */
+ private static MessageKey getMessageKey(boolean isRegistered) {
+ if (isRegistered) {
+ return MessageKey.LOGIN_MESSAGE;
+ } else {
+ return MessageKey.REGISTER_MESSAGE;
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java
new file mode 100644
index 000000000..e78ca3139
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java
@@ -0,0 +1,170 @@
+package fr.xephi.authme.data.limbo;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
+import fr.xephi.authme.settings.Settings;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_ALLOW_FLIGHT;
+import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_FLY_SPEED;
+import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_WALK_SPEED;
+
+/**
+ * Service for managing players that are in "limbo," a temporary state players are
+ * put in which have joined but not yet logged in yet.
+ */
+public class LimboService {
+
+ private final Map entries = new ConcurrentHashMap<>();
+
+ @Inject
+ private Settings settings;
+
+ @Inject
+ private LimboPlayerTaskManager taskManager;
+
+ @Inject
+ private LimboServiceHelper helper;
+
+ @Inject
+ private LimboPersistence persistence;
+
+ LimboService() {
+ }
+
+ /**
+ * Creates a LimboPlayer for the given player and revokes all "limbo data" from the player.
+ *
+ * @param player the player to process
+ * @param isRegistered whether or not the player is registered
+ */
+ public void createLimboPlayer(Player player, boolean isRegistered) {
+ final String name = player.getName().toLowerCase();
+
+ LimboPlayer limboFromDisk = persistence.getLimboPlayer(player);
+ if (limboFromDisk != null) {
+ ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name);
+ }
+
+ LimboPlayer existingLimbo = entries.remove(name);
+ if (existingLimbo != null) {
+ existingLimbo.clearTasks();
+ ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name);
+ }
+
+ LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
+ limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer);
+
+ taskManager.registerMessageTask(player, limboPlayer, isRegistered);
+ taskManager.registerTimeoutTask(player, limboPlayer);
+ helper.revokeLimboStates(player);
+ entries.put(name, limboPlayer);
+ persistence.saveLimboPlayer(player, limboPlayer);
+ }
+
+ /**
+ * Returns the limbo player for the given name, or null otherwise.
+ *
+ * @param name the name to retrieve the data for
+ * @return the associated limbo player, or null if none available
+ */
+ public LimboPlayer getLimboPlayer(String name) {
+ return entries.get(name.toLowerCase());
+ }
+
+ /**
+ * Returns whether there is a limbo player for the given name.
+ *
+ * @param name the name to check
+ * @return true if present, false otherwise
+ */
+ public boolean hasLimboPlayer(String name) {
+ return entries.containsKey(name.toLowerCase());
+ }
+
+ /**
+ * Restores the limbo data and subsequently deletes the entry.
+ *
+ * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and
+ * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}.
+ *
+ * @param player the player whose data should be restored
+ */
+ public void restoreData(Player player) {
+ String lowerName = player.getName().toLowerCase();
+ LimboPlayer limbo = entries.remove(lowerName);
+
+ if (limbo == null) {
+ ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName);
+ } else {
+ player.setOp(limbo.isOperator());
+ settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo);
+ settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo);
+ settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo);
+ limbo.clearTasks();
+ ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
+ persistence.removeLimboPlayer(player);
+ }
+ }
+
+ /**
+ * Creates new tasks for the given player and cancels the old ones for a newly registered player.
+ * This resets his time to log in (TimeoutTask) and updates the message he is shown (MessageTask).
+ *
+ * @param player the player to reset the tasks for
+ */
+ public void replaceTasksAfterRegistration(Player player) {
+ getLimboOrLogError(player, "reset tasks")
+ .ifPresent(limbo -> {
+ taskManager.registerTimeoutTask(player, limbo);
+ taskManager.registerMessageTask(player, limbo, true);
+ });
+ }
+
+ /**
+ * Resets the message task associated with the player's LimboPlayer.
+ *
+ * @param player the player to set a new message task for
+ * @param isRegistered whether or not the player is registered
+ */
+ public void resetMessageTask(Player player, boolean isRegistered) {
+ getLimboOrLogError(player, "reset message task")
+ .ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered));
+ }
+
+ /**
+ * @param player the player whose message task should be muted
+ */
+ public void muteMessageTask(Player player) {
+ getLimboOrLogError(player, "mute message task")
+ .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), true));
+ }
+
+ /**
+ * @param player the player whose message task should be unmuted
+ */
+ public void unmuteMessageTask(Player player) {
+ getLimboOrLogError(player, "unmute message task")
+ .ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false));
+ }
+
+ /**
+ * Returns the limbo player for the given player or logs an error.
+ *
+ * @param player the player to retrieve the limbo player for
+ * @param context the action for which the limbo player is being retrieved (for logging)
+ * @return Optional with the limbo player
+ */
+ private Optional getLimboOrLogError(Player player, String context) {
+ LimboPlayer limbo = entries.get(player.getName().toLowerCase());
+ if (limbo == null) {
+ ConsoleLogger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context);
+ }
+ return Optional.ofNullable(limbo);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
new file mode 100644
index 000000000..6b3acfcb5
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
@@ -0,0 +1,106 @@
+package fr.xephi.authme.data.limbo;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.permission.PermissionsManager;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.SpawnLoader;
+import fr.xephi.authme.settings.properties.RestrictionSettings;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+
+/**
+ * Helper class for the LimboService.
+ */
+class LimboServiceHelper {
+
+ @Inject
+ private SpawnLoader spawnLoader;
+
+ @Inject
+ private PermissionsManager permissionsManager;
+
+ @Inject
+ private Settings settings;
+
+ /**
+ * Creates a LimboPlayer with the given player's details.
+ *
+ * @param player the player to process
+ * @param isRegistered whether the player is registered
+ * @return limbo player with the player's data
+ */
+ LimboPlayer createLimboPlayer(Player player, boolean isRegistered) {
+ Location location = spawnLoader.getPlayerLocationOrSpawn(player);
+ // For safety reasons an unregistered player should not have OP status after registration
+ boolean isOperator = isRegistered && player.isOp();
+ boolean flyEnabled = player.getAllowFlight();
+ float walkSpeed = player.getWalkSpeed();
+ float flySpeed = player.getFlySpeed();
+ String playerGroup = permissionsManager.hasGroupSupport()
+ ? permissionsManager.getPrimaryGroup(player) : "";
+ ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup);
+
+ return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed);
+ }
+
+ /**
+ * Removes the data that is saved in a LimboPlayer from the player.
+ *
+ * Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and
+ * changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}.
+ *
+ * @param player the player to set defaults to
+ */
+ void revokeLimboStates(Player player) {
+ player.setOp(false);
+ player.setAllowFlight(false);
+
+ if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) {
+ player.setFlySpeed(0.0f);
+ player.setWalkSpeed(0.0f);
+ }
+ }
+
+ /**
+ * Merges two existing LimboPlayer instances of a player. Merging is done the following way:
+ *
+ * isOperator, allowFlight
: true if either limbo has true
+ * flySpeed, walkSpeed
: maximum value of either limbo player
+ * group, location
: from old limbo if not empty/null, otherwise from new limbo
+ *
+ *
+ * @param newLimbo the new limbo player
+ * @param oldLimbo the old limbo player
+ * @return merged limbo player if both arguments are not null, otherwise the first non-null argument
+ */
+ LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) {
+ if (newLimbo == null) {
+ return oldLimbo;
+ } else if (oldLimbo == null) {
+ return newLimbo;
+ }
+
+ boolean isOperator = newLimbo.isOperator() || oldLimbo.isOperator();
+ boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly();
+ float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed());
+ float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed());
+ String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup());
+ Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation());
+
+ return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed);
+ }
+
+ private static String firstNotEmpty(String newGroup, String oldGroup) {
+ ConsoleLogger.debug("Limbo merge: new and old perm groups are `{0}` and `{1}`", newGroup, oldGroup);
+ if ("".equals(oldGroup)) {
+ return newGroup;
+ }
+ return oldGroup;
+ }
+
+ private static Location firstNotNull(Location first, Location second) {
+ return first == null ? second : first;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java
new file mode 100644
index 000000000..960cdd435
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/WalkFlySpeedRestoreType.java
@@ -0,0 +1,81 @@
+package fr.xephi.authme.data.limbo;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Possible types to restore the walk and fly speed from LimboPlayer
+ * back to Bukkit Player.
+ */
+public enum WalkFlySpeedRestoreType {
+
+ /** Restores from LimboPlayer to Player. */
+ RESTORE {
+ @Override
+ public void restoreFlySpeed(Player player, LimboPlayer limbo) {
+ player.setFlySpeed(limbo.getFlySpeed());
+ }
+
+ @Override
+ public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
+ player.setWalkSpeed(limbo.getWalkSpeed());
+ }
+ },
+
+ /** Restores from LimboPlayer, using the default speed if the speed on LimboPlayer is 0. */
+ RESTORE_NO_ZERO {
+ @Override
+ public void restoreFlySpeed(Player player, LimboPlayer limbo) {
+ float limboFlySpeed = limbo.getFlySpeed();
+ player.setFlySpeed(limboFlySpeed > 0.01f ? limboFlySpeed : LimboPlayer.DEFAULT_FLY_SPEED);
+ }
+
+ @Override
+ public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
+ float limboWalkSpeed = limbo.getWalkSpeed();
+ player.setWalkSpeed(limboWalkSpeed > 0.01f ? limboWalkSpeed : LimboPlayer.DEFAULT_WALK_SPEED);
+ }
+ },
+
+ /** Uses the max speed of Player (current speed) and the LimboPlayer. */
+ MAX_RESTORE {
+ @Override
+ public void restoreFlySpeed(Player player, LimboPlayer limbo) {
+ player.setFlySpeed(Math.max(player.getFlySpeed(), limbo.getFlySpeed()));
+ }
+
+ @Override
+ public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
+ player.setWalkSpeed(Math.max(player.getWalkSpeed(), limbo.getWalkSpeed()));
+ }
+ },
+
+ /** Always sets the default speed to the player. */
+ DEFAULT {
+ @Override
+ public void restoreFlySpeed(Player player, LimboPlayer limbo) {
+ player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED);
+ }
+
+ @Override
+ public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
+ player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED);
+ }
+ };
+
+ /**
+ * Restores the fly speed from Limbo to Player according to the restoration type.
+ *
+ * @param player the player to modify
+ * @param limbo the limbo player to read from
+ */
+ public abstract void restoreFlySpeed(Player player, LimboPlayer limbo);
+
+ /**
+ * Restores the walk speed from Limbo to Player according to the restoration type.
+ *
+ * @param player the player to modify
+ * @param limbo the limbo player to read from
+ */
+ public abstract void restoreWalkSpeed(Player player, LimboPlayer limbo);
+
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java
new file mode 100644
index 000000000..f07e82cd3
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistence.java
@@ -0,0 +1,79 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.initialization.SettingsDependent;
+import fr.xephi.authme.initialization.factory.Factory;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.LimboSettings;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+
+/**
+ * Handles the persistence of LimboPlayers.
+ */
+public class LimboPersistence implements SettingsDependent {
+
+ private final Factory handlerFactory;
+
+ private LimboPersistenceHandler handler;
+
+ @Inject
+ LimboPersistence(Settings settings, Factory handlerFactory) {
+ this.handlerFactory = handlerFactory;
+ reload(settings);
+ }
+
+ /**
+ * Retrieves the LimboPlayer for the given player if available.
+ *
+ * @param player the player to retrieve the LimboPlayer for
+ * @return the player's limbo player, or null if not available
+ */
+ public LimboPlayer getLimboPlayer(Player player) {
+ try {
+ return handler.getLimboPlayer(player);
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e);
+ }
+ return null;
+ }
+
+ /**
+ * Saves the given LimboPlayer for the provided player.
+ *
+ * @param player the player to save the LimboPlayer for
+ * @param limbo the limbo player to save
+ */
+ public void saveLimboPlayer(Player player, LimboPlayer limbo) {
+ try {
+ handler.saveLimboPlayer(player, limbo);
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e);
+ }
+ }
+
+ /**
+ * Removes the LimboPlayer for the given player.
+ *
+ * @param player the player whose LimboPlayer should be removed
+ */
+ public void removeLimboPlayer(Player player) {
+ try {
+ handler.removeLimboPlayer(player);
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e);
+ }
+ }
+
+ @Override
+ public void reload(Settings settings) {
+ LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE);
+ // If we're changing from an existing handler, output a quick hint that nothing is converted.
+ if (handler != null && handler.getType() != persistenceType) {
+ ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted.");
+ }
+ handler = handlerFactory.newInstance(persistenceType.getImplementationClass());
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java
new file mode 100644
index 000000000..95e88aada
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceHandler.java
@@ -0,0 +1,39 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import org.bukkit.entity.Player;
+
+/**
+ * Handles I/O for storing LimboPlayer objects.
+ */
+interface LimboPersistenceHandler {
+
+ /**
+ * Returns the limbo player for the given player if it exists.
+ *
+ * @param player the player
+ * @return the stored limbo player, or null if not available
+ */
+ LimboPlayer getLimboPlayer(Player player);
+
+ /**
+ * Saves the given limbo player for the given player to the disk.
+ *
+ * @param player the player to save the limbo player for
+ * @param limbo the limbo player to save
+ */
+ void saveLimboPlayer(Player player, LimboPlayer limbo);
+
+ /**
+ * Removes the limbo player from the disk.
+ *
+ * @param player the player whose limbo player should be removed
+ */
+ void removeLimboPlayer(Player player);
+
+ /**
+ * @return the type of the limbo persistence implementation
+ */
+ LimboPersistenceType getType();
+
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java
new file mode 100644
index 000000000..68b4611bc
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceType.java
@@ -0,0 +1,37 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+/**
+ * Types of persistence for LimboPlayer objects.
+ */
+public enum LimboPersistenceType {
+
+ /** Store each LimboPlayer in a separate file. */
+ INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class),
+
+ /** Store all LimboPlayers in the same file. */
+ SINGLE_FILE(SingleFilePersistenceHandler.class),
+
+ /** Distribute LimboPlayers by segments into a set number of files. */
+ SEGMENT_FILES(SegmentFilesPersistenceHolder.class),
+
+ /** No persistence to disk. */
+ DISABLED(NoOpPersistenceHandler.class);
+
+ private final Class extends LimboPersistenceHandler> implementationClass;
+
+ /**
+ * Constructor.
+ *
+ * @param implementationClass the implementation class
+ */
+ LimboPersistenceType(Class extends LimboPersistenceHandler> implementationClass) {
+ this.implementationClass= implementationClass;
+ }
+
+ /**
+ * @return class implementing the persistence type
+ */
+ public Class extends LimboPersistenceHandler> getImplementationClass() {
+ return implementationClass;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java
new file mode 100644
index 000000000..94e1950b1
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java
@@ -0,0 +1,115 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.service.BukkitService;
+import org.bukkit.Location;
+import org.bukkit.World;
+
+import java.lang.reflect.Type;
+import java.util.function.Function;
+
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUP;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_PITCH;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_WORLD;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_X;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Y;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_YAW;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Z;
+import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.WALK_SPEED;
+
+/**
+ * Converts a JsonElement to a LimboPlayer.
+ */
+class LimboPlayerDeserializer implements JsonDeserializer {
+
+ private BukkitService bukkitService;
+
+ LimboPlayerDeserializer(BukkitService bukkitService) {
+ this.bukkitService = bukkitService;
+ }
+
+ @Override
+ public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ if (jsonObject == null) {
+ return null;
+ }
+
+ Location loc = deserializeLocation(jsonObject);
+ boolean operator = getBoolean(jsonObject, IS_OP);
+ String group = getString(jsonObject, GROUP);
+ boolean canFly = getBoolean(jsonObject, CAN_FLY);
+ float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED);
+ float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED);
+
+ return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
+ }
+
+ private Location deserializeLocation(JsonObject jsonObject) {
+ JsonElement e;
+ if ((e = jsonObject.getAsJsonObject(LOCATION)) != null) {
+ JsonObject locationObject = e.getAsJsonObject();
+ World world = bukkitService.getWorld(getString(locationObject, LOC_WORLD));
+ if (world != null) {
+ double x = getDouble(locationObject, LOC_X);
+ double y = getDouble(locationObject, LOC_Y);
+ double z = getDouble(locationObject, LOC_Z);
+ float yaw = getFloat(locationObject, LOC_YAW);
+ float pitch = getFloat(locationObject, LOC_PITCH);
+ return new Location(world, x, y, z, yaw, pitch);
+ }
+ }
+ return null;
+ }
+
+ private static String getString(JsonObject jsonObject, String memberName) {
+ JsonElement element = jsonObject.get(memberName);
+ return element != null ? element.getAsString() : "";
+ }
+
+ private static boolean getBoolean(JsonObject jsonObject, String memberName) {
+ JsonElement element = jsonObject.get(memberName);
+ return element != null && element.getAsBoolean();
+ }
+
+ private static float getFloat(JsonObject jsonObject, String memberName) {
+ return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, 0.0f);
+ }
+
+ private static float getFloat(JsonObject jsonObject, String memberName, float defaultValue) {
+ return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, defaultValue);
+ }
+
+ private static double getDouble(JsonObject jsonObject, String memberName) {
+ return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsDouble, 0.0);
+ }
+
+ /**
+ * Gets a number from the given JsonElement safely.
+ *
+ * @param jsonElement the element to retrieve the number from
+ * @param numberFunction the function to get the number from the element
+ * @param defaultValue the value to return if the element is null or the number cannot be retrieved
+ * @param the number type
+ * @return the number from the given JSON element, or the default value
+ */
+ private static N getNumberFromElement(JsonElement jsonElement,
+ Function numberFunction,
+ N defaultValue) {
+ if (jsonElement != null) {
+ try {
+ return numberFunction.apply(jsonElement);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ return defaultValue;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java
new file mode 100644
index 000000000..aeae3b65b
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java
@@ -0,0 +1,52 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import org.bukkit.Location;
+
+import java.lang.reflect.Type;
+
+/**
+ * Converts a LimboPlayer to a JsonElement.
+ */
+class LimboPlayerSerializer implements JsonSerializer {
+
+ static final String LOCATION = "location";
+ static final String LOC_WORLD = "world";
+ static final String LOC_X = "x";
+ static final String LOC_Y = "y";
+ static final String LOC_Z = "z";
+ static final String LOC_YAW = "yaw";
+ static final String LOC_PITCH = "pitch";
+
+ static final String GROUP = "group";
+ static final String IS_OP = "operator";
+ static final String CAN_FLY = "can-fly";
+ static final String WALK_SPEED = "walk-speed";
+ static final String FLY_SPEED = "fly-speed";
+
+
+ @Override
+ public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializationContext context) {
+ Location loc = limboPlayer.getLocation();
+ JsonObject locationObject = new JsonObject();
+ locationObject.addProperty(LOC_WORLD, loc.getWorld().getName());
+ locationObject.addProperty(LOC_X, loc.getX());
+ locationObject.addProperty(LOC_Y, loc.getY());
+ locationObject.addProperty(LOC_Z, loc.getZ());
+ locationObject.addProperty(LOC_YAW, loc.getYaw());
+ locationObject.addProperty(LOC_PITCH, loc.getPitch());
+
+ JsonObject obj = new JsonObject();
+ obj.add(LOCATION, locationObject);
+ obj.addProperty(GROUP, limboPlayer.getGroup());
+ obj.addProperty(IS_OP, limboPlayer.isOperator());
+ obj.addProperty(CAN_FLY, limboPlayer.isCanFly());
+ obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed());
+ obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed());
+ return obj;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java
new file mode 100644
index 000000000..ac6ff9b36
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/NoOpPersistenceHandler.java
@@ -0,0 +1,30 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import org.bukkit.entity.Player;
+
+/**
+ * Limbo player persistence implementation that does nothing.
+ */
+class NoOpPersistenceHandler implements LimboPersistenceHandler {
+
+ @Override
+ public LimboPlayer getLimboPlayer(Player player) {
+ return null;
+ }
+
+ @Override
+ public void saveLimboPlayer(Player player, LimboPlayer limbo) {
+ // noop
+ }
+
+ @Override
+ public void removeLimboPlayer(Player player) {
+ // noop
+ }
+
+ @Override
+ public LimboPersistenceType getType() {
+ return LimboPersistenceType.DISABLED;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java
new file mode 100644
index 000000000..5053ba521
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentConfiguration.java
@@ -0,0 +1,94 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+/**
+ * Configuration for the total number of segments to use.
+ *
+ * The {@link SegmentFilesPersistenceHolder} reduces the number of files by assigning each UUID
+ * to a segment. This enum allows to define how many segments the UUIDs should be distributed in.
+ *
+ * Segments are defined by a distribution and a length. The distribution defines
+ * to how many outputs a single hexadecimal characters should be mapped. So e.g. a distribution
+ * of 3 means that all hexadecimal characters 0-f should be distributed over three different
+ * outputs evenly. The {@link SegmentNameBuilder} simply uses hexadecimal characters as outputs,
+ * so e.g. with a distribution of 3 all hex characters 0-f are mapped to 0, 1, or 2.
+ *
+ * To ensure an even distribution the segments must be powers of 2. Trivially, to implement a
+ * distribution of 16, the same character may be returned as was input (since 0-f make up 16
+ * characters). A distribution of 1, on the other hand, means that the same output is returned
+ * regardless of the input character.
+ *
+ * The length parameter defines how many characters of a player's UUID should be used to
+ * create the segment ID. In other words, with a distribution of 2 and a length of 3, the first
+ * three characters of the UUID are taken into consideration, each mapped to one of two possible
+ * characters. For instance, a UUID starting with "0f5c9321" may yield the segment ID "010."
+ * Such a segment ID defines in which file the given UUID can be found and stored.
+ *
+ * The number of segments such a configuration yields is computed as {@code distribution ^ length},
+ * since distribution defines how many outputs there are per digit, and length defines the number
+ * of digits. For instance, a distribution of 2 and a length of 3 will yield segment IDs 000, 001,
+ * 010, 011, 100, 101, 110 and 111 (i.e. all binary numbers from 0 to 7).
+ *
+ * There are multiple possibilities to achieve certain segment totals, e.g. 8 different segments
+ * may be created by setting distribution to 8 and length to 1, or distr. to 2 and length to 3.
+ * Where possible, prefer a length of 1 (no string concatenation required) or a distribution of
+ * 16 (no remapping of the characters required).
+ */
+public enum SegmentConfiguration {
+
+ /** 1. */
+ ONE(1, 1),
+
+ ///** 2. */
+ //TWO(2, 1),
+
+ /** 4. */
+ FOUR(4, 1),
+
+ /** 8. */
+ EIGHT(8, 1),
+
+ /** 16. */
+ SIXTEEN(16, 1),
+
+ /** 32. */
+ THIRTY_TWO(2, 5),
+
+ /** 64. */
+ SIXTY_FOUR(4, 3),
+
+ /** 128. */
+ ONE_TWENTY(2, 7),
+
+ /** 256. */
+ TWO_FIFTY(16, 2);
+
+ private final int distribution;
+ private final int length;
+
+ SegmentConfiguration(int distribution, int length) {
+ this.distribution = distribution;
+ this.length = length;
+ }
+
+ /**
+ * @return the distribution size per character, i.e. how many possible outputs there are
+ * for any hexadecimal character
+ */
+ public int getDistribution() {
+ return distribution;
+ }
+
+ /**
+ * @return number of characters from a UUID that should be used to create a segment ID
+ */
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * @return number of segments to which this configuration will distribute UUIDs
+ */
+ public int getTotalSegments() {
+ return (int) Math.pow(distribution, length);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java
new file mode 100644
index 000000000..e786ca482
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentFilesPersistenceHolder.java
@@ -0,0 +1,226 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import com.google.common.io.Files;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.LimboSettings;
+import fr.xephi.authme.util.FileUtils;
+import fr.xephi.authme.util.PlayerUtils;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.FileWriter;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Persistence handler for LimboPlayer objects by distributing the objects to store
+ * in various segments (buckets) based on the start of the player's UUID.
+ */
+class SegmentFilesPersistenceHolder implements LimboPersistenceHandler {
+
+ private static final Type LIMBO_MAP_TYPE = new TypeToken>(){}.getType();
+
+ private final File cacheFolder;
+ private final Gson gson;
+ private final SegmentNameBuilder segmentNameBuilder;
+
+ @Inject
+ SegmentFilesPersistenceHolder(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) {
+ cacheFolder = new File(dataFolder, "playerdata");
+ if (!cacheFolder.exists()) {
+ // TODO ljacqu 20170313: Create FileUtils#mkdirs
+ cacheFolder.mkdirs();
+ }
+
+ gson = new GsonBuilder()
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
+ .setPrettyPrinting()
+ .create();
+
+ segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION));
+
+ convertOldDataToCurrentSegmentScheme();
+ deleteEmptyFiles();
+ }
+
+ @Override
+ public LimboPlayer getLimboPlayer(Player player) {
+ String uuid = PlayerUtils.getUUIDorName(player);
+ File file = getPlayerSegmentFile(uuid);
+ Map entries = readLimboPlayers(file);
+ return entries == null ? null : entries.get(uuid);
+ }
+
+ @Override
+ public void saveLimboPlayer(Player player, LimboPlayer limbo) {
+ String uuid = PlayerUtils.getUUIDorName(player);
+ File file = getPlayerSegmentFile(uuid);
+
+ Map entries = null;
+ if (file.exists()) {
+ entries = readLimboPlayers(file);
+ } else {
+ FileUtils.create(file);
+ }
+ /* intentionally separate if */
+ if (entries == null) {
+ entries = new HashMap<>();
+ }
+
+ entries.put(PlayerUtils.getUUIDorName(player), limbo);
+ saveEntries(entries, file);
+ }
+
+ @Override
+ public void removeLimboPlayer(Player player) {
+ String uuid = PlayerUtils.getUUIDorName(player);
+ File file = getPlayerSegmentFile(uuid);
+ if (file.exists()) {
+ Map entries = readLimboPlayers(file);
+ if (entries != null && entries.remove(PlayerUtils.getUUIDorName(player)) != null) {
+ saveEntries(entries, file);
+ }
+ }
+ }
+
+ @Override
+ public LimboPersistenceType getType() {
+ return LimboPersistenceType.SEGMENT_FILES;
+ }
+
+ private void saveEntries(Map entries, File file) {
+ try (FileWriter fw = new FileWriter(file)) {
+ gson.toJson(entries, fw);
+ } catch (Exception e) {
+ ConsoleLogger.logException("Could not write to '" + file + "':", e);
+ }
+ }
+
+ private Map readLimboPlayers(File file) {
+ if (!file.exists()) {
+ return null;
+ }
+
+ try {
+ return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE);
+ } catch (Exception e) {
+ ConsoleLogger.logException("Failed reading '" + file + "':", e);
+ }
+ return null;
+ }
+
+ private File getPlayerSegmentFile(String uuid) {
+ String segment = segmentNameBuilder.createSegmentName(uuid);
+ return new File(cacheFolder, segment + "-limbo.json");
+ }
+
+ /**
+ * Loads segment files in the cache folder that don't correspond to the current segmenting scheme
+ * and migrates the data into files of the current segments. This allows a player to change the
+ * segment size without any loss of data.
+ */
+ private void convertOldDataToCurrentSegmentScheme() {
+ String currentPrefix = segmentNameBuilder.getPrefix();
+ File[] files = listFiles(cacheFolder);
+ Map allLimboPlayers = new HashMap<>();
+ List migratedFiles = new ArrayList<>();
+
+ for (File file : files) {
+ if (isLimboJsonFile(file) && !file.getName().startsWith(currentPrefix)) {
+ Map data = readLimboPlayers(file);
+ if (data != null) {
+ allLimboPlayers.putAll(data);
+ migratedFiles.add(file);
+ }
+ }
+ }
+
+ if (!allLimboPlayers.isEmpty()) {
+ saveToNewSegments(allLimboPlayers);
+ migratedFiles.forEach(FileUtils::delete);
+ }
+ }
+
+ /**
+ * Saves the LimboPlayer data read from old segmenting schemes into the current segmenting scheme.
+ *
+ * @param limbosFromOldSegments the limbo players to store into the current segment files
+ */
+ private void saveToNewSegments(Map limbosFromOldSegments) {
+ Map> limboBySegment = groupBySegment(limbosFromOldSegments);
+
+ ConsoleLogger.info("Saving " + limbosFromOldSegments.size() + " LimboPlayers from old segments into "
+ + limboBySegment.size() + " current segments");
+ for (Map.Entry> entry : limboBySegment.entrySet()) {
+ File file = new File(cacheFolder, entry.getKey() + "-limbo.json");
+ Map limbosToSave = Optional.ofNullable(readLimboPlayers(file))
+ .orElseGet(HashMap::new);
+ limbosToSave.putAll(entry.getValue());
+ saveEntries(limbosToSave, file);
+ }
+ }
+
+ /**
+ * Converts a Map of UUID to LimboPlayers to a 2-dimensional Map of LimboPlayers by segment ID and UUID.
+ * {@code Map(uuid -> LimboPlayer) to Map(segment -> Map(uuid -> LimboPlayer))}
+ *
+ * @param readLimboPlayers the limbo players to order by segment
+ * @return limbo players ordered by segment ID and associated player UUID
+ */
+ private Map> groupBySegment(Map readLimboPlayers) {
+ Map> limboBySegment = new HashMap<>();
+ for (Map.Entry entry : readLimboPlayers.entrySet()) {
+ String segmentId = segmentNameBuilder.createSegmentName(entry.getKey());
+ limboBySegment.computeIfAbsent(segmentId, s -> new HashMap<>())
+ .put(entry.getKey(), entry.getValue());
+ }
+ return limboBySegment;
+ }
+
+ /**
+ * Deletes files from the current segmenting scheme that are empty.
+ */
+ private void deleteEmptyFiles() {
+ File[] files = listFiles(cacheFolder);
+
+ long deletedFiles = Arrays.stream(files)
+ // typically the size is 2 because there's an empty JSON map: {}
+ .filter(f -> isLimboJsonFile(f) && f.length() < 3)
+ .peek(FileUtils::delete)
+ .count();
+ ConsoleLogger.debug("Limbo: Deleted {0} empty segment files", deletedFiles);
+ }
+
+ /**
+ * @param file the file to check
+ * @return true if it is a segment file storing Limbo data, false otherwise
+ */
+ private static boolean isLimboJsonFile(File file) {
+ String name = file.getName();
+ return name.startsWith("seg") && name.endsWith("-limbo.json");
+ }
+
+ private static File[] listFiles(File folder) {
+ File[] files = folder.listFiles();
+ if (files == null) {
+ ConsoleLogger.warning("Could not get files of '" + folder + "'");
+ return new File[0];
+ }
+ return files;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java
new file mode 100644
index 000000000..52e1141bf
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SegmentNameBuilder.java
@@ -0,0 +1,73 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Creates segment names for {@link SegmentFilesPersistenceHolder}.
+ */
+class SegmentNameBuilder {
+
+ private final int length;
+ private final int distribution;
+ private final String prefix;
+ private final Map charToSegmentChar;
+
+ /**
+ * Constructor.
+ *
+ * @param partition the segment configuration
+ */
+ SegmentNameBuilder(SegmentConfiguration partition) {
+ this.length = partition.getLength();
+ this.distribution = partition.getDistribution();
+ this.prefix = "seg" + partition.getTotalSegments() + "-";
+ this.charToSegmentChar = buildCharMap(distribution);
+ }
+
+ /**
+ * Returns the segment ID for the given UUID.
+ *
+ * @param uuid the player's uuid to get the segment for
+ * @return id the uuid belongs to
+ */
+ String createSegmentName(String uuid) {
+ if (distribution == 16) {
+ return prefix + uuid.substring(0, length);
+ } else {
+ return prefix + buildSegmentName(uuid.substring(0, length).toCharArray());
+ }
+ }
+
+ /**
+ * @return the prefix used for the current segment configuration
+ */
+ String getPrefix() {
+ return prefix;
+ }
+
+ private String buildSegmentName(char[] chars) {
+ if (chars.length == 1) {
+ return String.valueOf(charToSegmentChar.get(chars[0]));
+ }
+
+ StringBuilder sb = new StringBuilder(chars.length);
+ for (char chr : chars) {
+ sb.append(charToSegmentChar.get(chr));
+ }
+ return sb.toString();
+ }
+
+ private static Map buildCharMap(int distribution) {
+ final char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ final int divisor = 16 / distribution;
+
+ Map charToSegmentChar = new HashMap<>();
+ for (int i = 0; i < hexChars.length; ++i) {
+ int mappedChar = i / divisor;
+ charToSegmentChar.put(hexChars[i], hexChars[mappedChar]);
+ }
+ return charToSegmentChar;
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java
new file mode 100644
index 000000000..438bce69d
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SeparateFilePersistenceHandler.java
@@ -0,0 +1,90 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import com.google.common.io.Files;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.util.FileUtils;
+import fr.xephi.authme.util.PlayerUtils;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Saves LimboPlayer objects as JSON into individual files.
+ */
+class SeparateFilePersistenceHandler implements LimboPersistenceHandler {
+
+ private final Gson gson;
+ private final File cacheDir;
+
+ @Inject
+ SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
+ cacheDir = new File(dataFolder, "playerdata");
+ if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
+ ConsoleLogger.warning("Failed to create playerdata directory '" + cacheDir + "'");
+ }
+ gson = new GsonBuilder()
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
+ .setPrettyPrinting()
+ .create();
+ }
+
+ @Override
+ public LimboPlayer getLimboPlayer(Player player) {
+ String id = PlayerUtils.getUUIDorName(player);
+ File file = new File(cacheDir, id + File.separator + "data.json");
+ if (!file.exists()) {
+ return null;
+ }
+
+ try {
+ String str = Files.toString(file, StandardCharsets.UTF_8);
+ return gson.fromJson(str, LimboPlayer.class);
+ } catch (IOException e) {
+ ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) {
+ String id = PlayerUtils.getUUIDorName(player);
+ try {
+ File file = new File(cacheDir, id + File.separator + "data.json");
+ Files.createParentDirs(file);
+ Files.touch(file);
+ Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ ConsoleLogger.logException("Failed to write " + player.getName() + " data:", e);
+ }
+ }
+
+ /**
+ * Removes the LimboPlayer. This will delete the
+ * "playerdata/<uuid or name>/" folder from disk.
+ *
+ * @param player player to remove
+ */
+ @Override
+ public void removeLimboPlayer(Player player) {
+ String id = PlayerUtils.getUUIDorName(player);
+ File file = new File(cacheDir, id);
+ if (file.exists()) {
+ FileUtils.purgeDirectory(file);
+ FileUtils.delete(file);
+ }
+ }
+
+ @Override
+ public LimboPersistenceType getType() {
+ return LimboPersistenceType.INDIVIDUAL_FILES;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java
new file mode 100644
index 000000000..57aa2dcff
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/SingleFilePersistenceHandler.java
@@ -0,0 +1,94 @@
+package fr.xephi.authme.data.limbo.persistence;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.util.FileUtils;
+import fr.xephi.authme.util.PlayerUtils;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Saves all LimboPlayers in one JSON file and keeps the entries in memory.
+ */
+class SingleFilePersistenceHandler implements LimboPersistenceHandler {
+
+ private final File cacheFile;
+ private final Gson gson;
+ private Map entries;
+
+ @Inject
+ SingleFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
+ cacheFile = new File(dataFolder, "limbo.json");
+ if (!cacheFile.exists()) {
+ FileUtils.create(cacheFile);
+ }
+
+ gson = new GsonBuilder()
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
+ .registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
+ .setPrettyPrinting()
+ .create();
+
+ Type type = new TypeToken>(){}.getType();
+ try (FileReader fr = new FileReader(cacheFile)) {
+ entries = gson.fromJson(fr, type);
+ } catch (IOException e) {
+ ConsoleLogger.logException("Failed to read from '" + cacheFile + "':", e);
+ }
+
+ if (entries == null) {
+ entries = new ConcurrentHashMap<>();
+ }
+ }
+
+ @Override
+ public LimboPlayer getLimboPlayer(Player player) {
+ return entries.get(PlayerUtils.getUUIDorName(player));
+ }
+
+ @Override
+ public void saveLimboPlayer(Player player, LimboPlayer limbo) {
+ entries.put(PlayerUtils.getUUIDorName(player), limbo);
+ saveEntries("adding '" + player.getName() + "'");
+ }
+
+ @Override
+ public void removeLimboPlayer(Player player) {
+ LimboPlayer entry = entries.remove(PlayerUtils.getUUIDorName(player));
+ if (entry != null) {
+ saveEntries("removing '" + player.getName() + "'");
+ }
+ }
+
+ /**
+ * Saves the entries to the disk.
+ *
+ * @param action the reason for saving (for logging purposes)
+ */
+ private void saveEntries(String action) {
+ try (FileWriter fw = new FileWriter(cacheFile)) {
+ gson.toJson(entries, fw);
+ } catch (IOException e) {
+ ConsoleLogger.logException("Failed saving JSON limbo cache after " + action, e);
+ }
+ }
+
+ @Override
+ public LimboPersistenceType getType() {
+ return LimboPersistenceType.SINGLE_FILE;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/Columns.java b/src/main/java/fr/xephi/authme/datasource/Columns.java
index b6d732cdb..f984007e3 100644
--- a/src/main/java/fr/xephi/authme/datasource/Columns.java
+++ b/src/main/java/fr/xephi/authme/datasource/Columns.java
@@ -6,6 +6,8 @@ import fr.xephi.authme.settings.properties.DatabaseSettings;
/**
* Database column names.
*/
+// Justification: String is immutable and this class is used to easily access the configurable column names
+@SuppressWarnings({"checkstyle:VisibilityModifier", "checkstyle:MemberName", "checkstyle:AbbreviationAsWordInName"})
public final class Columns {
public final String NAME;
diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index f500ac072..6e09b5853 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -7,7 +7,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.HashedPassword;
-import fr.xephi.authme.security.crypts.XFBCRYPT;
+import fr.xephi.authme.security.crypts.XfBCrypt;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.HooksSettings;
@@ -31,7 +31,7 @@ import java.util.Set;
public class MySQL implements DataSource {
- private boolean useSSL;
+ private boolean useSsl;
private String host;
private String port;
private String username;
@@ -45,7 +45,10 @@ public class MySQL implements DataSource {
private HikariDataSource ds;
private String phpBbPrefix;
+ private String ipbPrefix;
private int phpBbGroup;
+ private int ipbGroup;
+ private int xfGroup;
private String wordpressPrefix;
public MySQL(Settings settings) throws ClassNotFoundException, SQLException {
@@ -96,12 +99,15 @@ public class MySQL implements DataSource {
this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID);
+ this.ipbPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX);
+ this.ipbGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID);
+ this.xfGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID);
this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
if (poolSize == -1) {
- poolSize = Utils.getCoreCount();
+ poolSize = Utils.getCoreCount()*3;
}
- this.useSSL = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
+ this.useSsl = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
}
private void setConnectionArguments() {
@@ -119,7 +125,7 @@ public class MySQL implements DataSource {
ds.setPassword(this.password);
// Request mysql over SSL
- ds.addDataSourceProperty("useSSL", useSSL);
+ ds.addDataSourceProperty("useSSL", useSsl);
// Encoding
ds.addDataSourceProperty("characterEncoding", "utf8");
@@ -286,7 +292,7 @@ public class MySQL implements DataSource {
if (rs.next()) {
Blob blob = rs.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- auth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
+ auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
}
}
@@ -334,8 +340,39 @@ public class MySQL implements DataSource {
pst.close();
}
}
-
- if (hashAlgorithm == HashAlgorithm.PHPBB) {
+ if (hashAlgorithm == HashAlgorithm.IPB4){
+ sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
+ pst = con.prepareStatement(sql);
+ pst.setString(1, auth.getNickname());
+ rs = pst.executeQuery();
+ if (rs.next()){
+ // Update player group in core_members
+ sql = "UPDATE " + ipbPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;";
+ pst2 = con.prepareStatement(sql);
+ pst2.setInt(1, ipbGroup);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ pst2.close();
+ // Get current time without ms
+ long time = System.currentTimeMillis() / 1000;
+ // update joined date
+ sql = "UPDATE " + ipbPrefix + tableName + " SET "+ tableName + ".joined=? WHERE " + col.NAME + "=?;";
+ pst2 = con.prepareStatement(sql);
+ pst2.setLong(1, time);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ pst2.close();
+ // Update last_visit
+ sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;";
+ pst2 = con.prepareStatement(sql);
+ pst2.setLong(1, time);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ pst2.close();
+ }
+ rs.close();
+ pst.close();
+ } else if (hashAlgorithm == HashAlgorithm.PHPBB) {
sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
@@ -477,19 +514,53 @@ public class MySQL implements DataSource {
pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;");
pst.setString(1, auth.getNickname());
rs = pst.executeQuery();
- if (rs.next()) {
+ if (rs.next()) {
int id = rs.getInt(col.ID);
+ // Insert player password, salt in xf_user_authenticate
sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
- pst2.setString(2, XFBCRYPT.SCHEME_CLASS);
- String serializedHash = XFBCRYPT.serializeHash(auth.getPassword().getHash());
+ pst2.setString(2, XfBCrypt.SCHEME_CLASS);
+ String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash());
byte[] bytes = serializedHash.getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
pst2.setBlob(3, blob);
pst2.executeUpdate();
pst2.close();
+ // Update player group in xf_users
+ sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;";
+ pst2 = con.prepareStatement(sql);
+ pst2.setInt(1, xfGroup);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ pst2.close();
+ // Update player permission combination in xf_users
+ sql = "UPDATE " + tableName + " SET "+ tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;";
+ pst2 = con.prepareStatement(sql);
+ pst2.setInt(1, xfGroup);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ pst2.close();
+ // Insert player privacy combination in xf_user_privacy
+ sql = "INSERT INTO xf_user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)";
+ pst2 = con.prepareStatement(sql);
+ pst2.setInt(1, id);
+ pst2.setString(2, "everyone");
+ pst2.setString(3, "members");
+ pst2.setString(4, "members");
+ pst2.setString(5, "everyone");
+ pst2.setString(6, "everyone");
+ pst2.executeUpdate();
+ pst2.close();
+ // Insert player group relation in xf_user_group_relation
+ sql = "INSERT INTO xf_user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)";
+ pst2 = con.prepareStatement(sql);
+ pst2.setInt(1, id);
+ pst2.setInt(2, xfGroup);
+ pst2.setString(3, "1");
+ pst2.executeUpdate();
+ pst2.close();
}
rs.close();
pst.close();
@@ -538,7 +609,7 @@ public class MySQL implements DataSource {
// Insert password in the correct table
sql = "UPDATE xf_user_authenticate SET data=? WHERE " + col.ID + "=?;";
PreparedStatement pst2 = con.prepareStatement(sql);
- String serializedHash = XFBCRYPT.serializeHash(password.getHash());
+ String serializedHash = XfBCrypt.serializeHash(password.getHash());
byte[] bytes = serializedHash.getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
@@ -549,7 +620,7 @@ public class MySQL implements DataSource {
// ...
sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;";
pst2 = con.prepareStatement(sql);
- pst2.setString(1, XFBCRYPT.SCHEME_CLASS);
+ pst2.setString(1, XfBCrypt.SCHEME_CLASS);
pst2.setInt(2, id);
pst2.executeUpdate();
pst2.close();
@@ -824,7 +895,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
+ pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
rs2.close();
}
@@ -856,7 +927,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
+ pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
rs2.close();
}
diff --git a/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java
index d2be78047..8ca506250 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/AbstractDataSourceConverter.java
@@ -37,7 +37,7 @@ public abstract class AbstractDataSourceConverter implemen
// which is never the case when a converter is launched from the /authme converter command.
@Override
public void execute(CommandSender sender) {
- if (!destinationType.equals(destination.getType())) {
+ if (destinationType != destination.getType()) {
if (sender != null) {
sender.sendMessage("Please configure your connection to "
+ destinationType + " and re-run this command");
@@ -59,6 +59,7 @@ public abstract class AbstractDataSourceConverter implemen
if (destination.isAuthAvailable(auth.getNickname())) {
skippedPlayers.add(auth.getNickname());
} else {
+ adaptPlayerAuth(auth);
destination.saveAuth(auth);
destination.updateQuitLoc(auth);
}
@@ -72,6 +73,15 @@ public abstract class AbstractDataSourceConverter implemen
+ " to " + destinationType);
}
+ /**
+ * Adapts the PlayerAuth from the source before it is saved in the destination.
+ *
+ * @param auth the auth from the source
+ */
+ protected void adaptPlayerAuth(PlayerAuth auth) {
+ // noop
+ }
+
/**
* @return the data source to convert from
* @throws Exception during initialization of source
diff --git a/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java b/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java
index a52b216b6..1c67061f3 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/ForceFlatToSqlite.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.datasource.converter;
+import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.FlatFile;
@@ -25,4 +26,11 @@ public class ForceFlatToSqlite extends AbstractDataSourceConverter {
public FlatFile getSource() {
return source;
}
+
+ @Override
+ protected void adaptPlayerAuth(PlayerAuth auth) {
+ // Issue #1120: FlatFile returns PlayerAuth objects with realname = lower-case name all the time.
+ // We don't want to take this over into the new data source.
+ auth.setRealName("Player");
+ }
}
diff --git a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java
index eb9d904cd..4257f0aa7 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/RakamakConverter.java
@@ -16,6 +16,7 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
+import java.util.Map;
import java.util.Map.Entry;
/**
@@ -40,43 +41,41 @@ public class RakamakConverter implements Converter {
@Override
// TODO ljacqu 20151229: Restructure this into smaller portions
public void execute(CommandSender sender) {
- boolean useIP = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP);
+ boolean useIp = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP);
String fileName = settings.getProperty(ConverterSettings.RAKAMAK_FILE_NAME);
String ipFileName = settings.getProperty(ConverterSettings.RAKAMAK_IP_FILE_NAME);
File source = new File(pluginFolder, fileName);
- File ipfiles = new File(pluginFolder, ipFileName);
- HashMap playerIP = new HashMap<>();
- HashMap playerPSW = new HashMap<>();
+ File ipFiles = new File(pluginFolder, ipFileName);
+ Map playerIp = new HashMap<>();
+ Map playerPassword = new HashMap<>();
try {
- BufferedReader users;
- BufferedReader ipFile;
- ipFile = new BufferedReader(new FileReader(ipfiles));
+ BufferedReader ipFile = new BufferedReader(new FileReader(ipFiles));
String line;
- if (useIP) {
+ if (useIp) {
String tempLine;
while ((tempLine = ipFile.readLine()) != null) {
if (tempLine.contains("=")) {
String[] args = tempLine.split("=");
- playerIP.put(args[0], args[1]);
+ playerIp.put(args[0], args[1]);
}
}
}
ipFile.close();
- users = new BufferedReader(new FileReader(source));
+ BufferedReader users = new BufferedReader(new FileReader(source));
while ((line = users.readLine()) != null) {
if (line.contains("=")) {
String[] arguments = line.split("=");
HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]);
- playerPSW.put(arguments[0], hashedPassword);
+ playerPassword.put(arguments[0], hashedPassword);
}
}
users.close();
- for (Entry m : playerPSW.entrySet()) {
+ for (Entry m : playerPassword.entrySet()) {
String playerName = m.getKey();
- HashedPassword psw = playerPSW.get(playerName);
- String ip = useIP ? playerIP.get(playerName) : "127.0.0.1";
+ HashedPassword psw = playerPassword.get(playerName);
+ String ip = useIp ? playerIp.get(playerName) : "127.0.0.1";
PlayerAuth auth = PlayerAuth.builder()
.name(playerName)
.realName(playerName)
diff --git a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java
similarity index 95%
rename from src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java
rename to src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java
index 6d3de8b7d..ff88db2d7 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/vAuthConverter.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/VAuthConverter.java
@@ -16,13 +16,13 @@ import java.util.UUID;
import static fr.xephi.authme.util.FileUtils.makePath;
-public class vAuthConverter implements Converter {
+public class VAuthConverter implements Converter {
private final DataSource dataSource;
private final File vAuthPasswordsFile;
@Inject
- vAuthConverter(@DataFolder File dataFolder, DataSource dataSource) {
+ VAuthConverter(@DataFolder File dataFolder, DataSource dataSource) {
vAuthPasswordsFile = new File(dataFolder.getParent(), makePath("vAuth", "passwords.yml"));
this.dataSource = dataSource;
}
diff --git a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java
similarity index 96%
rename from src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java
rename to src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java
index 27fa6c2e9..97979c6c9 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/XAuthConverter.java
@@ -6,7 +6,7 @@ import de.luricos.bukkit.xAuth.xAuth;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.DataFolder;
-import fr.xephi.authme.util.CollectionUtils;
+import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.PluginManager;
@@ -21,7 +21,7 @@ import java.util.List;
import static fr.xephi.authme.util.FileUtils.makePath;
-public class xAuthConverter implements Converter {
+public class XAuthConverter implements Converter {
@Inject
@DataFolder
@@ -31,7 +31,7 @@ public class xAuthConverter implements Converter {
@Inject
private PluginManager pluginManager;
- xAuthConverter() {
+ XAuthConverter() {
}
@Override
@@ -55,7 +55,7 @@ public class xAuthConverter implements Converter {
sender.sendMessage("[AuthMe] xAuth H2 database not found, checking for MySQL or SQLite data...");
}
List players = getXAuthPlayers();
- if (CollectionUtils.isEmpty(players)) {
+ if (Utils.isCollectionEmpty(players)) {
sender.sendMessage("[AuthMe] Error while importing xAuthPlayers: did not find any players");
return;
}
diff --git a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java
index 1e7862443..e22eb9c5f 100644
--- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java
+++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java
@@ -2,15 +2,14 @@ package fr.xephi.authme.initialization;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.data.backup.LimboPlayerStorage;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.PluginHookService;
+import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.RestrictionSettings;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.service.ValidationService;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -28,17 +27,15 @@ public class OnShutdownPlayerSaver {
@Inject
private ValidationService validationService;
@Inject
- private LimboCache limboCache;
- @Inject
private DataSource dataSource;
@Inject
- private LimboPlayerStorage limboPlayerStorage;
- @Inject
private SpawnLoader spawnLoader;
@Inject
private PluginHookService pluginHookService;
@Inject
private PlayerCache playerCache;
+ @Inject
+ private LimboService limboService;
OnShutdownPlayerSaver() {
}
@@ -57,9 +54,8 @@ public class OnShutdownPlayerSaver {
if (pluginHookService.isNpc(player) || validationService.isUnrestricted(name)) {
return;
}
- if (limboCache.hasPlayerData(name)) {
- limboCache.restoreData(player);
- limboCache.removeFromCache(player);
+ if (limboService.hasLimboPlayer(name)) {
+ limboService.restoreData(player);
} else {
saveLoggedinPlayer(player);
}
@@ -75,9 +71,5 @@ public class OnShutdownPlayerSaver {
.location(loc).build();
dataSource.updateQuitLoc(auth);
}
- if (settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)
- && !settings.getProperty(RestrictionSettings.NO_TELEPORT) && !limboPlayerStorage.hasData(player)) {
- limboPlayerStorage.saveData(player);
- }
}
}
diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
index 0dd421817..bce15af47 100644
--- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
+++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
@@ -1,11 +1,16 @@
package fr.xephi.authme.initialization;
+import ch.jalu.injector.exceptions.InjectorReflectionException;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
+import fr.xephi.authme.security.HashAlgorithm;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+import org.bstats.Metrics;
import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.service.BukkitService;
@@ -18,10 +23,9 @@ import fr.xephi.authme.util.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
-import org.mcstats.Metrics;
import javax.inject.Inject;
-import java.io.IOException;
+import java.util.Optional;
import java.util.logging.Logger;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
@@ -44,41 +48,35 @@ public class OnStartupTasks {
OnStartupTasks() {
}
+ /**
+ * Sends bstats metrics.
+ *
+ * @param plugin the plugin instance
+ * @param settings the settings
+ */
public static void sendMetrics(AuthMe plugin, Settings settings) {
- try {
- final Metrics metrics = new Metrics(plugin);
+ final Metrics metrics = new Metrics(plugin);
- final Metrics.Graph languageGraph = metrics.createGraph("Messages Language");
- final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
- languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) {
- @Override
- public int getValue() {
- return 1;
- }
- });
+ metrics.addCustomChart(new Metrics.SimplePie("messages_language") {
+ @Override
+ public String getValue() {
+ return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
+ }
+ });
- final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend");
- final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString();
- databaseBackend.addPlotter(new Metrics.Plotter(dataSource) {
- @Override
- public int getValue() {
- return 1;
- }
- });
-
- // Submit metrics
- metrics.start();
- } catch (IOException e) {
- // Failed to submit the metrics data
- ConsoleLogger.logException("Can't send Metrics data! The plugin will work anyway...", e);
- }
+ metrics.addCustomChart(new Metrics.SimplePie("database_backend") {
+ @Override
+ public String getValue() {
+ return settings.getProperty(DatabaseSettings.BACKEND).toString();
+ }
+ });
}
/**
* Sets up the console filter if enabled.
*
* @param settings the settings
- * @param logger the plugin logger
+ * @param logger the plugin logger
*/
public static void setupConsoleFilter(Settings settings, Logger logger) {
if (!settings.getProperty(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE)) {
@@ -124,4 +122,42 @@ public class OnStartupTasks {
}
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL));
}
+
+ /**
+ * Displays a hint to use the legacy AuthMe JAR if AuthMe could not be started
+ * because Gson was not found.
+ *
+ * @param e the exception to process
+ */
+ public static void displayLegacyJarHint(Exception e) {
+ if (e instanceof InjectorReflectionException) {
+ Throwable causeOfCause = Optional.of(e)
+ .map(Throwable::getCause)
+ .map(Throwable::getCause).orElse(null);
+ if (causeOfCause instanceof NoClassDefFoundError
+ && "Lcom/google/gson/Gson;".equals(causeOfCause.getMessage())) {
+ ConsoleLogger.warning("YOU MUST DOWNLOAD THE LEGACY JAR TO USE AUTHME ON YOUR SERVER");
+ ConsoleLogger.warning("Get authme-legacy.jar from http://ci.xephi.fr/job/AuthMeReloaded/");
+ }
+ }
+ }
+
+ /**
+ * Returns whether the hash algorithm is deprecated and won't be able
+ * to be actively used anymore in 5.4.
+ *
+ * @param hash the hash algorithm to check
+ * @return true if the hash will be deprecated, false otherwise
+ * @see #1016
+ */
+ public static boolean isHashDeprecatedIn54(HashAlgorithm hash) {
+ if (hash.getClazz() == null || hash == HashAlgorithm.PLAINTEXT) {
+ // Exclude PLAINTEXT from this check because it already has a mandatory migration, which takes care of
+ // sending all the necessary messages and warnings.
+ return false;
+ }
+
+ Recommendation recommendation = hash.getClazz().getAnnotation(Recommendation.class);
+ return recommendation != null && recommendation.value() == Usage.DEPRECATED;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/Factory.java b/src/main/java/fr/xephi/authme/initialization/factory/Factory.java
new file mode 100644
index 000000000..0f4ae62ad
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/initialization/factory/Factory.java
@@ -0,0 +1,19 @@
+package fr.xephi.authme.initialization.factory;
+
+/**
+ * Injectable factory that creates new instances of a certain type.
+ *
+ * @param the parent type to which the factory is limited to
+ */
+public interface Factory
{
+
+ /**
+ * Creates an instance of the given class.
+ *
+ * @param clazz the class to instantiate
+ * @param the class type
+ * @return new instance of the class
+ */
+ C newInstance(Class clazz);
+
+}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java
new file mode 100644
index 000000000..e063d1497
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java
@@ -0,0 +1,46 @@
+package fr.xephi.authme.initialization.factory;
+
+import ch.jalu.injector.Injector;
+import ch.jalu.injector.context.ResolvedInstantiationContext;
+import ch.jalu.injector.handlers.dependency.DependencyHandler;
+import ch.jalu.injector.handlers.instantiation.DependencyDescription;
+import ch.jalu.injector.utils.ReflectionUtils;
+
+/**
+ * Dependency handler that builds {@link Factory} objects.
+ */
+public class FactoryDependencyHandler implements DependencyHandler {
+
+ @Override
+ public Object resolveValue(ResolvedInstantiationContext> context, DependencyDescription dependencyDescription) {
+ if (dependencyDescription.getType() == Factory.class) {
+ Class> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
+ if (genericType == null) {
+ throw new IllegalStateException("Factory fields must have concrete generic type. "
+ + "Cannot get generic type for field in '" + context.getMappedClass() + "'");
+ }
+
+ return new FactoryImpl<>(genericType, context.getInjector());
+ }
+ return null;
+ }
+
+ private static final class FactoryImpl implements Factory
{
+
+ private final Injector injector;
+ private final Class
parentClass;
+
+ FactoryImpl(Class
parentClass, Injector injector) {
+ this.parentClass = parentClass;
+ this.injector = injector;
+ }
+
+ @Override
+ public C newInstance(Class clazz) {
+ if (parentClass.isAssignableFrom(clazz)) {
+ return injector.newInstance(clazz);
+ }
+ throw new IllegalArgumentException(clazz + " not child of " + parentClass);
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java
new file mode 100644
index 000000000..13a5cbb50
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java
@@ -0,0 +1,37 @@
+package fr.xephi.authme.initialization.factory;
+
+import java.util.Collection;
+
+/**
+ * Injectable object to retrieve and create singletons of a common parent.
+ *
+ * @param the parent class to which this store is limited to
+ */
+public interface SingletonStore
{
+
+ /**
+ * Returns the singleton of the given type, creating it if it hasn't been yet created.
+ *
+ * @param clazz the class to get the singleton for
+ * @param type of the singleton
+ * @return the singleton of type {@code C}
+ */
+ C getSingleton(Class clazz);
+
+ /**
+ * Returns all existing singletons of this store's type.
+ *
+ * @return all registered singletons of type {@code P}
+ */
+ Collection retrieveAllOfType();
+
+ /**
+ * Returns all existing singletons of the given type.
+ *
+ * @param clazz the type to get singletons for
+ * @param class type
+ * @return all registered singletons of type {@code C}
+ */
+ Collection retrieveAllOfType(Class clazz);
+
+}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java
new file mode 100644
index 000000000..09a198c72
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java
@@ -0,0 +1,61 @@
+package fr.xephi.authme.initialization.factory;
+
+import ch.jalu.injector.Injector;
+import ch.jalu.injector.context.ResolvedInstantiationContext;
+import ch.jalu.injector.handlers.dependency.DependencyHandler;
+import ch.jalu.injector.handlers.instantiation.DependencyDescription;
+import ch.jalu.injector.utils.ReflectionUtils;
+
+import java.util.Collection;
+
+/**
+ * Dependency handler that builds {@link SingletonStore} objects.
+ */
+public class SingletonStoreDependencyHandler implements DependencyHandler {
+
+ @Override
+ public Object resolveValue(ResolvedInstantiationContext> context, DependencyDescription dependencyDescription) {
+ if (dependencyDescription.getType() == SingletonStore.class) {
+ Class> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
+ if (genericType == null) {
+ throw new IllegalStateException("Singleton store fields must have concrete generic type. "
+ + "Cannot get generic type for field in '" + context.getMappedClass() + "'");
+ }
+
+ return new SingletonStoreImpl<>(genericType, context.getInjector());
+ }
+ return null;
+ }
+
+ private static final class SingletonStoreImpl implements SingletonStore
{
+
+ private final Injector injector;
+ private final Class
parentClass;
+
+ SingletonStoreImpl(Class
parentClass, Injector injector) {
+ this.parentClass = parentClass;
+ this.injector = injector;
+ }
+
+ @Override
+ public C getSingleton(Class clazz) {
+ if (parentClass.isAssignableFrom(clazz)) {
+ return injector.getSingleton(clazz);
+ }
+ throw new IllegalArgumentException(clazz + " not child of " + parentClass);
+ }
+
+ @Override
+ public Collection retrieveAllOfType() {
+ return retrieveAllOfType(parentClass);
+ }
+
+ @Override
+ public Collection retrieveAllOfType(Class clazz) {
+ if (parentClass.isAssignableFrom(clazz)) {
+ return injector.retrieveAllOfType(clazz);
+ }
+ throw new IllegalArgumentException(clazz + " not child of " + parentClass);
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
index e3ff4ec5b..c7644e083 100644
--- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
+++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
@@ -29,7 +29,7 @@ import java.util.regex.Pattern;
/**
* Service for performing various verifications when a player joins.
*/
-class OnJoinVerifier implements Reloadable {
+public class OnJoinVerifier implements Reloadable {
@Inject
private Settings settings;
@@ -111,7 +111,7 @@ class OnJoinVerifier implements Reloadable {
* @param event the login event to verify
*
* @return true if the player's connection should be refused (i.e. the event does not need to be processed
- * further), false if the player is not refused
+ * further), false if the player is not refused
*/
public boolean refusePlayerForFullServer(PlayerLoginEvent event) {
final Player player = event.getPlayer();
diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java
index ddd3e430d..71fa2fc1f 100644
--- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java
+++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java
@@ -7,6 +7,7 @@ import fr.xephi.authme.message.Messages;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.AntiBotService;
import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.JoinMessageService;
import fr.xephi.authme.service.TeleportationService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.Settings;
@@ -54,8 +55,6 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAU
*/
public class PlayerListener implements Listener {
- public static final Map joinMessage = new ConcurrentHashMap<>();
-
@Inject
private Settings settings;
@Inject
@@ -78,6 +77,8 @@ public class PlayerListener implements Listener {
private TeleportationService teleportationService;
@Inject
private ValidationService validationService;
+ @Inject
+ private JoinMessageService joinMessageService;
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
@@ -175,7 +176,7 @@ public class PlayerListener implements Listener {
String customJoinMessage = settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE);
if (!customJoinMessage.isEmpty()) {
event.setJoinMessage(customJoinMessage.replace("{PLAYERNAME}", player.getName())
- .replace("{DISPLAYNAME]", player.getDisplayName()));
+ .replace("{DISPLAYNAME}", player.getDisplayName()));
}
if (!settings.getProperty(RegistrationSettings.DELAY_JOIN_MESSAGE)) {
@@ -188,7 +189,7 @@ public class PlayerListener implements Listener {
// Remove the join message while the player isn't logging in
if (joinMsg != null) {
event.setJoinMessage(null);
- joinMessage.put(name, joinMsg);
+ joinMessageService.putMessage(name, joinMsg);
}
}
diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java
index 48cf1868e..051507651 100644
--- a/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java
+++ b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java
@@ -14,6 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
package fr.xephi.authme.listener.protocollib;
import com.comphenix.protocol.PacketType;
@@ -46,7 +47,7 @@ class InventoryPacketAdapter extends PacketAdapter {
private static final int MAIN_SIZE = 27;
private static final int HOTBAR_SIZE = 9;
- public InventoryPacketAdapter(AuthMe plugin) {
+ InventoryPacketAdapter(AuthMe plugin) {
super(plugin, PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS);
}
diff --git a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java
index 4476f80ad..daf686389 100644
--- a/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java
+++ b/src/main/java/fr/xephi/authme/listener/protocollib/TabCompletePacketAdapter.java
@@ -12,7 +12,7 @@ import fr.xephi.authme.data.auth.PlayerCache;
class TabCompletePacketAdapter extends PacketAdapter {
- public TabCompletePacketAdapter(AuthMe plugin) {
+ TabCompletePacketAdapter(AuthMe plugin) {
super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE);
}
diff --git a/src/main/java/fr/xephi/authme/mail/EmailService.java b/src/main/java/fr/xephi/authme/mail/EmailService.java
new file mode 100644
index 000000000..5eb588d18
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/mail/EmailService.java
@@ -0,0 +1,125 @@
+package fr.xephi.authme.mail;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.EmailSettings;
+import fr.xephi.authme.settings.properties.SecuritySettings;
+import fr.xephi.authme.util.FileUtils;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.bukkit.Server;
+
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.imageio.ImageIO;
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Creates emails and sends them.
+ */
+public class EmailService {
+
+ private final File dataFolder;
+ private final String serverName;
+ private final Settings settings;
+ private final SendMailSsl sendMailSsl;
+
+ @Inject
+ EmailService(@DataFolder File dataFolder, Server server, Settings settings, SendMailSsl sendMailSsl) {
+ this.dataFolder = dataFolder;
+ this.serverName = server.getServerName();
+ this.settings = settings;
+ this.sendMailSsl = sendMailSsl;
+ }
+
+ public boolean hasAllInformation() {
+ return sendMailSsl.hasAllInformation();
+ }
+
+
+ /**
+ * Sends an email to the user with his new password.
+ *
+ * @param name the name of the player
+ * @param mailAddress the player's email
+ * @param newPass the new password
+ * @return true if email could be sent, false otherwise
+ */
+ public boolean sendPasswordMail(String name, String mailAddress, String newPass) {
+ if (!hasAllInformation()) {
+ ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
+ return false;
+ }
+
+ HtmlEmail email;
+ try {
+ email = sendMailSsl.initializeMail(mailAddress);
+ } catch (EmailException e) {
+ ConsoleLogger.logException("Failed to create email with the given settings:", e);
+ return false;
+ }
+
+ String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass);
+ // Generate an image?
+ File file = null;
+ if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
+ try {
+ file = generateImage(name, newPass);
+ mailText = embedImageIntoEmailContent(file, email, mailText);
+ } catch (IOException | EmailException e) {
+ ConsoleLogger.logException(
+ "Unable to send new password as image for email " + mailAddress + ":", e);
+ }
+ }
+
+ boolean couldSendEmail = sendMailSsl.sendEmail(mailText, email);
+ FileUtils.delete(file);
+ return couldSendEmail;
+ }
+
+ public boolean sendRecoveryCode(String name, String email, String code) {
+ HtmlEmail htmlEmail;
+ try {
+ htmlEmail = sendMailSsl.initializeMail(email);
+ } catch (EmailException e) {
+ ConsoleLogger.logException("Failed to create email for recovery code:", e);
+ return false;
+ }
+
+ String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(),
+ name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID));
+ return sendMailSsl.sendEmail(message, htmlEmail);
+ }
+
+ private File generateImage(String name, String newPass) throws IOException {
+ ImageGenerator gen = new ImageGenerator(newPass);
+ File file = new File(dataFolder, name + "_new_pass.jpg");
+ ImageIO.write(gen.generateImage(), "jpg", file);
+ return file;
+ }
+
+ private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content)
+ throws EmailException {
+ DataSource source = new FileDataSource(image);
+ String tag = email.embed(source, image.getName());
+ return content.replace(" ", " ");
+ }
+
+ private String replaceTagsForPasswordMail(String mailText, String name, String newPass) {
+ return mailText
+ .replace(" ", name)
+ .replace(" ", serverName)
+ .replace(" ", newPass);
+ }
+
+ private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) {
+ return mailText
+ .replace(" ", name)
+ .replace(" ", serverName)
+ .replace(" ", code)
+ .replace(" ", String.valueOf(hoursValid));
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java b/src/main/java/fr/xephi/authme/mail/SendMailSsl.java
similarity index 54%
rename from src/main/java/fr/xephi/authme/mail/SendMailSSL.java
rename to src/main/java/fr/xephi/authme/mail/SendMailSsl.java
index a782d3a13..34011595f 100644
--- a/src/main/java/fr/xephi/authme/mail/SendMailSSL.java
+++ b/src/main/java/fr/xephi/authme/mail/SendMailSsl.java
@@ -1,27 +1,19 @@
package fr.xephi.authme.mail;
-import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
-import fr.xephi.authme.settings.properties.SecuritySettings;
-import fr.xephi.authme.util.FileUtils;
+import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailConstants;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
-import org.bukkit.Server;
import javax.activation.CommandMap;
-import javax.activation.DataSource;
-import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
-import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.mail.Session;
-import java.io.File;
-import java.io.IOException;
import java.security.Security;
import java.util.Properties;
@@ -32,18 +24,10 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
/**
* Sends emails to players on behalf of the server.
*/
-public class SendMailSSL {
-
- private final File dataFolder;
- private final String serverName;
- private final Settings settings;
+public class SendMailSsl {
@Inject
- SendMailSSL(@DataFolder File dataFolder, Server server, Settings settings) {
- this.dataFolder = dataFolder;
- this.serverName = server.getServerName();
- this.settings = settings;
- }
+ private Settings settings;
/**
* Returns whether all necessary settings are set for sending mails.
@@ -55,76 +39,7 @@ public class SendMailSSL {
&& !settings.getProperty(MAIL_PASSWORD).isEmpty();
}
- /**
- * Sends an email to the user with his new password.
- *
- * @param name the name of the player
- * @param mailAddress the player's email
- * @param newPass the new password
- * @return true if email could be sent, false otherwise
- */
- public boolean sendPasswordMail(String name, String mailAddress, String newPass) {
- if (!hasAllInformation()) {
- ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
- return false;
- }
-
- HtmlEmail email;
- try {
- email = initializeMail(mailAddress);
- } catch (EmailException e) {
- ConsoleLogger.logException("Failed to create email with the given settings:", e);
- return false;
- }
-
- String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass);
- // Generate an image?
- File file = null;
- if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
- try {
- file = generateImage(name, newPass);
- mailText = embedImageIntoEmailContent(file, email, mailText);
- } catch (IOException | EmailException e) {
- ConsoleLogger.logException(
- "Unable to send new password as image for email " + mailAddress + ":", e);
- }
- }
-
- boolean couldSendEmail = sendEmail(mailText, email);
- FileUtils.delete(file);
- return couldSendEmail;
- }
-
- public boolean sendRecoveryCode(String name, String email, String code) {
- HtmlEmail htmlEmail;
- try {
- htmlEmail = initializeMail(email);
- } catch (EmailException e) {
- ConsoleLogger.logException("Failed to create email for recovery code:", e);
- return false;
- }
-
- String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(),
- name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID));
- return sendEmail(message, htmlEmail);
- }
-
- private File generateImage(String name, String newPass) throws IOException {
- ImageGenerator gen = new ImageGenerator(newPass);
- File file = new File(dataFolder, name + "_new_pass.jpg");
- ImageIO.write(gen.generateImage(), "jpg", file);
- return file;
- }
-
- private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content)
- throws EmailException {
- DataSource source = new FileDataSource(image);
- String tag = email.embed(source, image.getName());
- return content.replace(" ", " ");
- }
-
- @VisibleForTesting
- HtmlEmail initializeMail(String emailAddress) throws EmailException {
+ public HtmlEmail initializeMail(String emailAddress) throws EmailException {
String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS))
? settings.getProperty(EmailSettings.MAIL_ACCOUNT)
: settings.getProperty(EmailSettings.MAIL_ADDRESS);
@@ -143,14 +58,16 @@ public class SendMailSSL {
email.setFrom(senderMail, senderName);
email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT));
email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword);
+ if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) {
+ email.setDebug(true);
+ }
setPropertiesForPort(email, port);
return email;
}
- @VisibleForTesting
- boolean sendEmail(String content, HtmlEmail email) {
- Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader());
+ public boolean sendEmail(String content, HtmlEmail email) {
+ Thread.currentThread().setContextClassLoader(SendMailSsl.class.getClassLoader());
// Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative
// cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
@@ -176,21 +93,6 @@ public class SendMailSSL {
}
}
- private String replaceTagsForPasswordMail(String mailText, String name, String newPass) {
- return mailText
- .replace(" ", name)
- .replace(" ", serverName)
- .replace(" ", newPass);
- }
-
- private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) {
- return mailText
- .replace(" ", name)
- .replace(" ", serverName)
- .replace(" ", code)
- .replace(" ", String.valueOf(hoursValid));
- }
-
private void setPropertiesForPort(HtmlEmail email, int port) throws EmailException {
switch (port) {
case 587:
@@ -214,8 +116,10 @@ public class SendMailSSL {
}
break;
case 25:
- email.setStartTLSEnabled(true);
- email.setSSLCheckServerIdentity(true);
+ if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) {
+ email.setStartTLSEnabled(true);
+ email.setSSLCheckServerIdentity(true);
+ }
break;
case 465:
email.setSslSmtpPort(Integer.toString(port));
diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java
index a0d4e947c..5d19e8f40 100644
--- a/src/main/java/fr/xephi/authme/message/MessageFileHandler.java
+++ b/src/main/java/fr/xephi/authme/message/MessageFileHandler.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.message;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.util.FileUtils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -16,6 +17,7 @@ public class MessageFileHandler {
// regular file
private final String filename;
private final FileConfiguration configuration;
+ private final String updateAddition;
// default file
private final String defaultFile;
private FileConfiguration defaultConfiguration;
@@ -25,11 +27,15 @@ public class MessageFileHandler {
*
* @param file the file to use for messages
* @param defaultFile the default file from the JAR to use if no message is found
+ * @param updateCommand command to update the messages file (nullable) to show in error messages
*/
- public MessageFileHandler(File file, String defaultFile) {
+ public MessageFileHandler(File file, String defaultFile, String updateCommand) {
this.filename = file.getName();
this.configuration = YamlConfiguration.loadConfiguration(file);
this.defaultFile = defaultFile;
+ this.updateAddition = updateCommand == null
+ ? ""
+ : " (or run " + updateCommand + ")";
}
/**
@@ -53,7 +59,7 @@ public class MessageFileHandler {
if (message == null) {
ConsoleLogger.warning("Error getting message with key '" + key + "'. "
- + "Please update your config file '" + filename + "' (or run /authme messages)");
+ + "Please update your config file '" + filename + "'" + updateAddition);
return getDefault(key);
}
return message;
@@ -78,7 +84,7 @@ public class MessageFileHandler {
*/
private String getDefault(String key) {
if (defaultConfiguration == null) {
- InputStream stream = MessageFileHandler.class.getClassLoader().getResourceAsStream(defaultFile);
+ InputStream stream = FileUtils.getResourceFromJar(defaultFile);
defaultConfiguration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
}
String message = defaultConfiguration.getString(key);
diff --git a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java
index 987caeccc..5b809b2c9 100644
--- a/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java
+++ b/src/main/java/fr/xephi/authme/message/MessageFileHandlerProvider.java
@@ -36,10 +36,23 @@ public class MessageFileHandlerProvider {
* @return the message file handler
*/
public MessageFileHandler initializeHandler(Function pathBuilder) {
+ return initializeHandler(pathBuilder, null);
+ }
+
+ /**
+ * Initializes a message file handler with the messages file of the configured language.
+ * Ensures beforehand that the messages file exists or creates it otherwise.
+ *
+ * @param pathBuilder function taking the configured language code as argument and returning the messages file
+ * @param updateCommand command to run to update the languages file (nullable)
+ * @return the message file handler
+ */
+ public MessageFileHandler initializeHandler(Function pathBuilder, String updateCommand) {
String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
return new MessageFileHandler(
initializeFile(language, pathBuilder),
- pathBuilder.apply(DEFAULT_LANGUAGE));
+ pathBuilder.apply(DEFAULT_LANGUAGE),
+ updateCommand);
}
/**
@@ -53,7 +66,8 @@ public class MessageFileHandlerProvider {
File initializeFile(String language, Function pathBuilder) {
String filePath = pathBuilder.apply(language);
File file = new File(dataFolder, filePath);
- if (FileUtils.copyFileFromResource(file, filePath)) {
+ // Check that JAR file exists to avoid logging an error
+ if (FileUtils.getResourceFromJar(filePath) != null && FileUtils.copyFileFromResource(file, filePath)) {
return file;
}
diff --git a/src/main/java/fr/xephi/authme/message/MessageKey.java b/src/main/java/fr/xephi/authme/message/MessageKey.java
index 6cc57480f..f962ba4e1 100644
--- a/src/main/java/fr/xephi/authme/message/MessageKey.java
+++ b/src/main/java/fr/xephi/authme/message/MessageKey.java
@@ -17,16 +17,13 @@ public enum MessageKey {
/** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */
KICK_ANTIBOT("kick_antibot"),
- /** Can't find the requested user in the database! */
+ /** This user isn't registered! */
UNKNOWN_USER("unknown_user"),
- /** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */
- UNSAFE_QUIT_LOCATION("unsafe_spawn"),
-
/** You're not logged in! */
NOT_LOGGED_IN("not_logged_in"),
- /** Usage: /login <password> */
+ /** Usage: /login <password> */
USAGE_LOGIN("usage_log"),
/** Wrong password! */
@@ -56,19 +53,19 @@ public enum MessageKey {
/** An unexpected error occurred, please contact an administrator! */
ERROR("error"),
- /** Please, login with the command "/login <password>" */
+ /** Please, login with the command: /login <password> */
LOGIN_MESSAGE("login_msg"),
- /** Please, register to the server with the command "/register <password> <ConfirmPassword>" */
+ /** Please, register to the server with the command: /register <password> <ConfirmPassword> */
REGISTER_MESSAGE("reg_msg"),
/** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */
MAX_REGISTER_EXCEEDED("max_reg", "%max_acc", "%reg_count", "%reg_names"),
- /** Usage: /register <password> <ConfirmPassword> */
+ /** Usage: /register <password> <ConfirmPassword> */
USAGE_REGISTER("usage_reg"),
- /** Usage: /unregister <password> */
+ /** Usage: /unregister <password> */
USAGE_UNREGISTER("usage_unreg"),
/** Password changed successfully! */
@@ -95,7 +92,7 @@ public enum MessageKey {
/** You're already logged in! */
ALREADY_LOGGED_IN_ERROR("logged_in"),
- /** Logged-out successfully! */
+ /** Logged out successfully! */
LOGOUT_SUCCESS("logout"),
/** The same username is already playing on the server! */
@@ -113,7 +110,7 @@ public enum MessageKey {
/** Login timeout exceeded, you have been kicked from the server, please try again! */
LOGIN_TIMEOUT_ERROR("timeout"),
- /** Usage: /changepassword <oldPassword> <newPassword> */
+ /** Usage: /changepassword <oldPassword> <newPassword> */
USAGE_CHANGE_PASSWORD("usage_changepassword"),
/** Your username is either too short or too long! */
@@ -122,13 +119,13 @@ public enum MessageKey {
/** Your username contains illegal characters. Allowed chars: REG_EX */
INVALID_NAME_CHARACTERS("regex", "REG_EX"),
- /** Please add your email to your account with the command "/email add <yourEmail> <confirmEmail>" */
+ /** Please add your email to your account with the command: /email add <yourEmail> <confirmEmail> */
ADD_EMAIL_MESSAGE("add_email"),
- /** Forgot your password? Please use the command "/email recovery <yourEmail>" */
+ /** Forgot your password? Please use the command: /email recovery <yourEmail> */
FORGOT_PASSWORD_MESSAGE("recovery_email"),
- /** To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>" */
+ /** To login you have to solve a captcha code, please use the command: /captcha <theCaptcha> */
USAGE_CAPTCHA("usage_captcha", ""),
/** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */
@@ -143,13 +140,13 @@ public enum MessageKey {
/** The server is full, try again later! */
KICK_FULL_SERVER("kick_fullserver"),
- /** Usage: /email add <email> <confirmEmail> */
+ /** Usage: /email add <email> <confirmEmail> */
USAGE_ADD_EMAIL("usage_email_add"),
- /** Usage: /email change <oldEmail> <newEmail> */
+ /** Usage: /email change <oldEmail> <newEmail> */
USAGE_CHANGE_EMAIL("usage_email_change"),
- /** Usage: /email recovery <Email> */
+ /** Usage: /email recovery <Email> */
USAGE_RECOVER_EMAIL("usage_email_recovery"),
/** Invalid new email, try again! */
@@ -179,9 +176,6 @@ public enum MessageKey {
/** Recovery email sent successfully! Please check your email inbox! */
RECOVERY_EMAIL_SENT_MESSAGE("email_send"),
- /** A recovery email was already sent! You can discard it and send a new one using the command below: */
- RECOVERY_EMAIL_ALREADY_SENT_MESSAGE("email_exists"),
-
/** Your country is banned from this server! */
COUNTRY_BANNED_ERROR("country_banned"),
@@ -224,8 +218,51 @@ public enum MessageKey {
/** A recovery code to reset your password has been sent to your email. */
RECOVERY_CODE_SENT("recovery_code_sent"),
- /** The recovery code is not correct! Use /email recovery [email] to generate a new one */
- INCORRECT_RECOVERY_CODE("recovery_code_incorrect");
+ /** The recovery code is not correct! You have %count tries remaining. */
+ INCORRECT_RECOVERY_CODE("recovery_code_incorrect", "%count"),
+
+ /**
+ * You have exceeded the maximum number of attempts to enter the recovery code.
+ * Use "/email recovery [email]" to generate a new one.
+ */
+ RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"),
+
+ /** Recovery code entered correctly! */
+ RECOVERY_CODE_CORRECT("recovery_code_correct"),
+
+ /** Please use the command /email setpassword to change your password immediately. */
+ RECOVERY_CHANGE_PASSWORD("recovery_change_password"),
+
+ /** You cannot change your password using this command anymore. */
+ CHANGE_PASSWORD_EXPIRED("change_password_expired"),
+
+ /** An email was already sent recently. You must wait %time before you can send a new one. */
+ EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"),
+
+ /** second */
+ SECOND("second"),
+
+ /** seconds */
+ SECONDS("seconds"),
+
+ /** minute */
+ MINUTE("minute"),
+
+ /** minutes */
+ MINUTES("minutes"),
+
+ /** hour */
+ HOUR("hour"),
+
+ /** hours */
+ HOURS("hours"),
+
+ /** day */
+ DAY("day"),
+
+ /** days */
+ DAYS("days");
+
private String key;
private String[] tags;
diff --git a/src/main/java/fr/xephi/authme/message/Messages.java b/src/main/java/fr/xephi/authme/message/Messages.java
index 86fa2db6f..d7fdcec89 100644
--- a/src/main/java/fr/xephi/authme/message/Messages.java
+++ b/src/main/java/fr/xephi/authme/message/Messages.java
@@ -1,11 +1,15 @@
package fr.xephi.authme.message;
+import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.util.expiring.Duration;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Class for retrieving and sending translatable messages to players.
@@ -15,6 +19,20 @@ public class Messages implements Reloadable {
// Custom Authme tag replaced to new line
private static final String NEWLINE_TAG = "%nl%";
+ /** Contains the keys of the singular messages for time units. */
+ private static final Map TIME_UNIT_SINGULARS = ImmutableMap.builder()
+ .put(TimeUnit.SECONDS, MessageKey.SECOND)
+ .put(TimeUnit.MINUTES, MessageKey.MINUTE)
+ .put(TimeUnit.HOURS, MessageKey.HOUR)
+ .put(TimeUnit.DAYS, MessageKey.DAY).build();
+
+ /** Contains the keys of the plural messages for time units. */
+ private static final Map TIME_UNIT_PLURALS = ImmutableMap.builder()
+ .put(TimeUnit.SECONDS, MessageKey.SECONDS)
+ .put(TimeUnit.MINUTES, MessageKey.MINUTES)
+ .put(TimeUnit.HOURS, MessageKey.HOURS)
+ .put(TimeUnit.DAYS, MessageKey.DAYS).build();
+
private final MessageFileHandlerProvider messageFileHandlerProvider;
private MessageFileHandler messageFileHandler;
@@ -71,6 +89,22 @@ public class Messages implements Reloadable {
return message.split("\n");
}
+ /**
+ * Returns the textual representation for the given duration.
+ * Note that this class only supports the time units days, hour, minutes and seconds.
+ *
+ * @param duration the duration to build a text of
+ * @return text of the duration
+ */
+ public String formatDuration(Duration duration) {
+ long value = duration.getDuration();
+ MessageKey timeUnitKey = value == 1
+ ? TIME_UNIT_SINGULARS.get(duration.getTimeUnit())
+ : TIME_UNIT_PLURALS.get(duration.getTimeUnit());
+
+ return value + " " + retrieveMessage(timeUnitKey);
+ }
+
/**
* Retrieve the message from the text file.
*
@@ -107,7 +141,7 @@ public class Messages implements Reloadable {
@Override
public void reload() {
this.messageFileHandler = messageFileHandlerProvider
- .initializeHandler(lang -> "messages/messages_" + lang + ".yml");
+ .initializeHandler(lang -> "messages/messages_" + lang + ".yml", "/authme messages");
}
private static String formatMessage(String message) {
diff --git a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java
index 605283ac2..7d46daf84 100644
--- a/src/main/java/fr/xephi/authme/output/LogFilterHelper.java
+++ b/src/main/java/fr/xephi/authme/output/LogFilterHelper.java
@@ -1,17 +1,24 @@
package fr.xephi.authme.output;
+import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.util.StringUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* Service class for the log filters.
*/
-public final class LogFilterHelper {
+final class LogFilterHelper {
private static final String ISSUED_COMMAND_TEXT = "issued server command:";
- private static final String[] COMMANDS_TO_SKIP = {"/login ", "/l ", "/reg ", "/changepassword ",
- "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ",
- "/register "};
+ @VisibleForTesting
+ static final List COMMANDS_TO_SKIP = withAndWithoutAuthMePrefix(
+ "/login ", "/l ", "/log ", "/register ", "/reg ", "/unregister ", "/unreg ",
+ "/changepassword ", "/cp ", "/changepass ", "/authme register ", "/authme reg ", "/authme r ",
+ "/authme changepassword ", "/authme password ", "/authme changepass ", "/authme cp ");
private LogFilterHelper() {
// Util class
@@ -24,11 +31,20 @@ public final class LogFilterHelper {
*
* @return True if it is a sensitive AuthMe command, false otherwise
*/
- public static boolean isSensitiveAuthMeCommand(String message) {
+ static boolean isSensitiveAuthMeCommand(String message) {
if (message == null) {
return false;
}
String lowerMessage = message.toLowerCase();
return lowerMessage.contains(ISSUED_COMMAND_TEXT) && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP);
}
+
+ private static List withAndWithoutAuthMePrefix(String... commands) {
+ List commandList = new ArrayList<>(commands.length * 2);
+ for (String command : commands) {
+ commandList.add(command);
+ commandList.add(command.substring(0, 1) + "authme:" + command.substring(1));
+ }
+ return Collections.unmodifiableList(commandList);
+ }
}
diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java
index 537c3ebc5..2759b636a 100644
--- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java
@@ -1,21 +1,28 @@
package fr.xephi.authme.permission;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.settings.Settings;
-import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
-import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
-import java.util.Arrays;
+import java.util.Optional;
/**
* Changes the permission group according to the auth status of the player and the configuration.
+ *
+ * If this feature is enabled, the primary permissions group of a player is replaced until he has
+ * logged in. Some permission plugins have a notion of a primary group; for other permission plugins the
+ * first group is simply taken.
+ *
+ * The groups that are used as replacement until the player logs in is configurable and depends on if
+ * the player is registered or not. Note that some (all?) permission systems require the group to actually
+ * exist for the replacement to take place. Furthermore, since some permission groups require that players
+ * be in at least one group, this will mean that the player is not removed from his primary group.
*/
public class AuthGroupHandler implements Reloadable {
@@ -26,9 +33,8 @@ public class AuthGroupHandler implements Reloadable {
private Settings settings;
@Inject
- private LimboCache limboCache;
+ private LimboService limboService;
- private String unloggedInGroup;
private String unregisteredGroup;
private String registeredGroup;
@@ -36,15 +42,53 @@ public class AuthGroupHandler implements Reloadable {
}
/**
- * Set the group of a player, by its AuthMe group type.
+ * Sets the group of a player by its authentication status.
*
- * @param player The player.
- * @param group The group type.
- *
- * @return True if succeeded, false otherwise. False is also returned if groups aren't supported
- * with the current permissions system.
+ * @param player the player
+ * @param groupType the group type
*/
- public boolean setGroup(Player player, AuthGroupType group) {
+ public void setGroup(Player player, AuthGroupType groupType) {
+ if (!useAuthGroups()) {
+ return;
+ }
+
+ String primaryGroup = Optional
+ .ofNullable(limboService.getLimboPlayer(player.getName()))
+ .map(LimboPlayer::getGroup)
+ .orElse("");
+
+ switch (groupType) {
+ // Implementation note: some permission systems don't support players not being in any group,
+ // so add the new group before removing the old ones
+ case UNREGISTERED:
+ permissionsManager.addGroup(player, unregisteredGroup);
+ permissionsManager.removeGroups(player, registeredGroup, primaryGroup);
+ break;
+
+ case REGISTERED_UNAUTHENTICATED:
+ permissionsManager.addGroup(player, registeredGroup);
+ permissionsManager.removeGroups(player, unregisteredGroup, primaryGroup);
+ break;
+
+ case LOGGED_IN:
+ permissionsManager.addGroup(player, primaryGroup);
+ permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup);
+ break;
+
+ default:
+ throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'");
+ }
+
+ ConsoleLogger.debug(() -> player.getName() + " changed to "
+ + groupType + ": has groups " + permissionsManager.getGroups(player));
+ }
+
+ /**
+ * Returns whether the auth permissions group function should be used.
+ *
+ * @return true if should be used, false otherwise
+ */
+ private boolean useAuthGroups() {
// Check whether the permissions check is enabled
if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
return false;
@@ -55,72 +99,14 @@ public class AuthGroupHandler implements Reloadable {
ConsoleLogger.warning("The current permissions system doesn't have group support, unable to set group!");
return false;
}
-
- switch (group) {
- case UNREGISTERED:
- // Remove the other group type groups, set the current group
- permissionsManager.removeGroups(player, Arrays.asList(registeredGroup, unloggedInGroup));
- return permissionsManager.addGroup(player, unregisteredGroup);
-
- case REGISTERED:
- // Remove the other group type groups, set the current group
- permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, unloggedInGroup));
- return permissionsManager.addGroup(player, registeredGroup);
-
- case NOT_LOGGED_IN:
- // Remove the other group type groups, set the current group
- permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup));
- return permissionsManager.addGroup(player, unloggedInGroup);
-
- case LOGGED_IN:
- // Get the player data
- LimboPlayer data = limboCache.getPlayerData(player.getName().toLowerCase());
- if (data == null) {
- return false;
- }
-
- // Get the players group
- String realGroup = data.getGroup();
-
- // Remove the other group types groups, set the real group
- permissionsManager.removeGroups(player,
- Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup)
- );
- return permissionsManager.addGroup(player, realGroup);
- default:
- return false;
- }
- }
-
- /**
- * TODO: This method requires better explanation.
- *
- * Set the normal group of a player.
- *
- * @param player The player.
- * @param group The normal group.
- *
- * @return True on success, false on failure.
- */
- public boolean addNormal(Player player, String group) {
- // Check whether the permissions check is enabled
- if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
- return false;
- }
-
- // Remove old groups
- permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup));
-
- // Add the normal group, return the result
- return permissionsManager.addGroup(player, group);
+ return true;
}
@Override
@PostConstruct
public void reload() {
- unloggedInGroup = settings.getProperty(SecuritySettings.UNLOGGEDIN_GROUP);
- unregisteredGroup = settings.getProperty(HooksSettings.UNREGISTERED_GROUP);
- registeredGroup = settings.getProperty(HooksSettings.REGISTERED_GROUP);
+ unregisteredGroup = settings.getProperty(PluginSettings.UNREGISTERED_GROUP);
+ registeredGroup = settings.getProperty(PluginSettings.REGISTERED_GROUP);
}
}
diff --git a/src/main/java/fr/xephi/authme/permission/AuthGroupType.java b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java
index 9ab2a370c..dfedf8ee0 100644
--- a/src/main/java/fr/xephi/authme/permission/AuthGroupType.java
+++ b/src/main/java/fr/xephi/authme/permission/AuthGroupType.java
@@ -8,11 +8,8 @@ public enum AuthGroupType {
/** Player does not have an account. */
UNREGISTERED,
- /** Registered? */
- REGISTERED, // TODO #761: Remove this or the NOT_LOGGED_IN one
-
- /** Player is registered and not logged in. */
- NOT_LOGGED_IN,
+ /** Player is registered but not logged in. */
+ REGISTERED_UNAUTHENTICATED,
/** Player is logged in. */
LOGGED_IN
diff --git a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java
index 7ce0b3e5a..aac2f4bba 100644
--- a/src/main/java/fr/xephi/authme/permission/PermissionsManager.java
+++ b/src/main/java/fr/xephi/authme/permission/PermissionsManager.java
@@ -19,8 +19,8 @@ import org.bukkit.plugin.PluginManager;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
+import java.util.Collections;
/**
*
@@ -74,7 +74,7 @@ public class PermissionsManager implements Reloadable {
// Loop through all the available permissions system types
for (PermissionsSystemType type : PermissionsSystemType.values()) {
try {
- PermissionHandler handler = getPermissionHandler(type);
+ PermissionHandler handler = createPermissionHandler(type);
if (handler != null) {
// Show a success message and return
this.handler = handler;
@@ -91,7 +91,14 @@ public class PermissionsManager implements Reloadable {
ConsoleLogger.info("No supported permissions system found! Permissions are disabled!");
}
- private PermissionHandler getPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException {
+ /**
+ * Creates a permission handler for the provided permission systems if possible.
+ *
+ * @param type the permission systems type for which to create a corresponding permission handler
+ * @return the permission handler, or {@code null} if not possible
+ * @throws PermissionHandlerException during initialization of the permission handler
+ */
+ private PermissionHandler createPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException {
// Try to find the plugin for the current permissions system
Plugin plugin = pluginManager.getPlugin(type.getPluginName());
@@ -255,12 +262,12 @@ public class PermissionsManager implements Reloadable {
*
* @param player The player.
*
- * @return Permission groups, or an empty list if this feature is not supported.
+ * @return Permission groups, or an empty collection if this feature is not supported.
*/
- public List getGroups(Player player) {
+ public Collection getGroups(Player player) {
// If no permissions system is used, return an empty list
if (!isEnabled())
- return new ArrayList<>();
+ return Collections.emptyList();
return handler.getGroups(player);
}
@@ -289,7 +296,7 @@ public class PermissionsManager implements Reloadable {
* @return True if the player is in the specified group, false otherwise.
* False is also returned if groups aren't supported by the used permissions system.
*/
- public boolean inGroup(Player player, String groupName) {
+ public boolean isInGroup(Player player, String groupName) {
// If no permissions system is used, return false
if (!isEnabled())
return false;
@@ -307,42 +314,12 @@ public class PermissionsManager implements Reloadable {
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean addGroup(Player player, String groupName) {
- if (StringUtils.isEmpty(groupName)) {
+ if (!isEnabled() || StringUtils.isEmpty(groupName)) {
return false;
}
-
- // If no permissions system is used, return false
- if (!isEnabled()) {
- return false;
- }
-
return handler.addToGroup(player, groupName);
}
- /**
- * Add the permission groups of a player, if supported.
- *
- * @param player The player
- * @param groupNames The name of the groups to add.
- *
- * @return True if succeed, false otherwise.
- * False is also returned if this feature isn't supported for the current permissions system.
- */
- public boolean addGroups(Player player, List groupNames) {
- // If no permissions system is used, return false
- if (!isEnabled())
- return false;
-
- // Add each group to the user
- boolean result = true;
- for (String groupName : groupNames)
- if (!addGroup(player, groupName))
- result = false;
-
- // Return the result
- return result;
- }
-
/**
* Remove the permission group of a player, if supported.
*
@@ -352,8 +329,7 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
- public boolean removeGroup(Player player, String groupName) {
- // If no permissions system is used, return false
+ public boolean removeGroups(Player player, String groupName) {
if (!isEnabled())
return false;
@@ -369,16 +345,18 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
- public boolean removeGroups(Player player, List groupNames) {
+ public boolean removeGroups(Player player, String... groupNames) {
// If no permissions system is used, return false
if (!isEnabled())
return false;
// Add each group to the user
boolean result = true;
- for (String groupName : groupNames)
- if (!removeGroup(player, groupName))
+ for (String groupName : groupNames) {
+ if (!handler.removeFromGroup(player, groupName)) {
result = false;
+ }
+ }
// Return the result
return result;
@@ -402,41 +380,6 @@ public class PermissionsManager implements Reloadable {
return handler.setGroup(player, groupName);
}
- /**
- * Set the permission groups of a player, if supported.
- * This clears the current groups of the player.
- *
- * @param player The player
- * @param groupNames The name of the groups to set.
- *
- * @return True if succeed, false otherwise.
- * False is also returned if this feature isn't supported for the current permissions system.
- */
- public boolean setGroups(Player player, List groupNames) {
- // If no permissions system is used or if there's no group supplied, return false
- if (!isEnabled() || groupNames.isEmpty())
- return false;
-
- // Set the main group
- if (!setGroup(player, groupNames.get(0)))
- return false;
-
- // Add the rest of the groups
- boolean result = true;
- for (int i = 1; i < groupNames.size(); i++) {
- // Get the group name
- String groupName = groupNames.get(i);
-
- // Add this group
- if (!addGroup(player, groupName)) {
- result = false;
- }
- }
-
- // Return the result
- return result;
- }
-
/**
* Remove all groups of the specified player, if supported.
* Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay
@@ -453,9 +396,9 @@ public class PermissionsManager implements Reloadable {
return false;
// Get a list of current groups
- List groupNames = getGroups(player);
+ Collection groupNames = getGroups(player);
// Remove each group
- return removeGroups(player, groupNames);
+ return removeGroups(player, groupNames.toArray(new String[groupNames.size()]));
}
}
diff --git a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java
index aaeb0eea3..2160eea56 100644
--- a/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java
+++ b/src/main/java/fr/xephi/authme/permission/PlayerStatePermission.java
@@ -29,7 +29,12 @@ public enum PlayerStatePermission implements PermissionNode {
/**
* Permission to bypass the purging process.
*/
- BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED);
+ BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED),
+
+ /**
+ * Permission to use the /authme debug command.
+ */
+ DEBUG_COMMAND("authme.debug", DefaultPermission.OP_ONLY);
/**
* The permission node.
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java
index 9f52214b9..849ecd657 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/BPermissionsHandler.java
@@ -9,6 +9,12 @@ import org.bukkit.entity.Player;
import java.util.Arrays;
import java.util.List;
+/**
+ * Handler for bPermissions.
+ *
+ * @see bPermissions Bukkit page
+ * @see bPermissions on Github
+ */
public class BPermissionsHandler implements PermissionHandler {
@Override
@@ -49,19 +55,6 @@ public class BPermissionsHandler implements PermissionHandler {
return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName()));
}
- @Override
- public String getPrimaryGroup(Player player) {
- // Get the groups of the player
- List groups = getGroups(player);
-
- // Make sure there is any group available, or return null
- if (groups.isEmpty())
- return null;
-
- // Return the first group
- return groups.get(0);
- }
-
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.B_PERMISSIONS;
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java
index 71c7a2879..50816ce01 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionHandler.java
@@ -2,9 +2,10 @@ package fr.xephi.authme.permission.handlers;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsSystemType;
+import fr.xephi.authme.util.Utils;
import org.bukkit.entity.Player;
-import java.util.List;
+import java.util.Collection;
public interface PermissionHandler {
@@ -16,7 +17,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
- * False is also returned if this feature isn't supported for the current permissions system.
+ * False is also returned if this feature isn't supported for the current permissions system.
*/
boolean addToGroup(Player player, String group);
@@ -46,9 +47,11 @@ public interface PermissionHandler {
* @param group The group name.
*
* @return True if the player is in the specified group, false otherwise.
- * False is also returned if groups aren't supported by the used permissions system.
+ * False is also returned if groups aren't supported by the used permissions system.
*/
- boolean isInGroup(Player player, String group);
+ default boolean isInGroup(Player player, String group) {
+ return getGroups(player).contains(group);
+ }
/**
* Remove the permission group of a player, if supported.
@@ -57,7 +60,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
- * False is also returned if this feature isn't supported for the current permissions system.
+ * False is also returned if this feature isn't supported for the current permissions system.
*/
boolean removeFromGroup(Player player, String group);
@@ -69,7 +72,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
- * False is also returned if this feature isn't supported for the current permissions system.
+ * False is also returned if this feature isn't supported for the current permissions system.
*/
boolean setGroup(Player player, String group);
@@ -80,7 +83,7 @@ public interface PermissionHandler {
*
* @return Permission groups, or an empty list if this feature is not supported.
*/
- List getGroups(Player player);
+ Collection getGroups(Player player);
/**
* Get the primary group of a player, if available.
@@ -89,7 +92,13 @@ public interface PermissionHandler {
*
* @return The name of the primary permission group. Or null.
*/
- String getPrimaryGroup(Player player);
+ default String getPrimaryGroup(Player player) {
+ Collection groups = getGroups(player);
+ if (Utils.isCollectionEmpty(groups)) {
+ return null;
+ }
+ return groups.iterator().next();
+ }
/**
* Get the permission system that is being used.
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java
index c215b8d17..acae466c1 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsBukkitHandler.java
@@ -12,6 +12,11 @@ import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.List;
+/**
+ * Handler for PermissionsBukkit.
+ *
+ * @see PermissionsBukkit Bukkit page
+ */
public class PermissionsBukkitHandler implements PermissionHandler {
private PermissionsPlugin permissionsBukkitInstance;
@@ -26,7 +31,8 @@ public class PermissionsBukkitHandler implements PermissionHandler {
@Override
public boolean addToGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player addgroup " + player.getName() + " " + group);
}
@Override
@@ -39,46 +45,27 @@ public class PermissionsBukkitHandler implements PermissionHandler {
return false;
}
- @Override
- public boolean isInGroup(Player player, String group) {
- List groupNames = getGroups(player);
-
- return groupNames.contains(group);
- }
-
@Override
public boolean removeFromGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player removegroup " + player.getName() + " " + group);
}
@Override
public boolean setGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player setgroup " + player.getName() + " " + group);
}
@Override
public List getGroups(Player player) {
- List groups = new ArrayList();
+ List groups = new ArrayList<>();
for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) {
groups.add(group.getName());
}
return groups;
}
- @Override
- public String getPrimaryGroup(Player player) {
- // Get the groups of the player
- List groups = getGroups(player);
-
- // Make sure there is any group available, or return null
- if (groups.isEmpty()) {
- return null;
- }
-
- // Return the first group
- return groups.get(0);
- }
-
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_BUKKIT;
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java
index b11d00003..9b4550d84 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/PermissionsExHandler.java
@@ -10,6 +10,12 @@ import ru.tehkode.permissions.bukkit.PermissionsEx;
import java.util.ArrayList;
import java.util.List;
+/**
+ * Handler for PermissionsEx.
+ *
+ * @see PermissionsEx Bukkit page
+ * @see PermissionsEx on Github
+ */
public class PermissionsExHandler implements PermissionHandler {
private PermissionManager permissionManager;
@@ -72,17 +78,6 @@ public class PermissionsExHandler implements PermissionHandler {
return user.getParentIdentifiers(null);
}
- @Override
- public String getPrimaryGroup(Player player) {
- PermissionUser user = permissionManager.getUser(player);
-
- List groups = user.getParentIdentifiers(null);
- if (groups.isEmpty())
- return null;
-
- return groups.get(0);
- }
-
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_EX;
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java
index 8955569ec..79badc53e 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/VaultHandler.java
@@ -10,6 +10,12 @@ import org.bukkit.plugin.RegisteredServiceProvider;
import java.util.Arrays;
import java.util.List;
+/**
+ * Handler for permissions via Vault.
+ *
+ * @see Vault Bukkit page
+ * @see Vault on Github
+ */
public class VaultHandler implements PermissionHandler {
private Permission vaultProvider;
diff --git a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java
index 498ba4a91..c86863f78 100644
--- a/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/handlers/ZPermissionsHandler.java
@@ -6,10 +6,15 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
import java.util.Map;
+/**
+ * Handler for zPermissions.
+ *
+ * @see zPermissions Bukkit page
+ * @see zPermissions on Github
+ */
public class ZPermissionsHandler implements PermissionHandler {
private ZPermissionsService zPermissionsService;
@@ -25,7 +30,8 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override
public boolean addToGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player " + player.getName() + " addgroup " + group);
}
@Override
@@ -36,31 +42,29 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override
public boolean hasPermissionOffline(String name, PermissionNode node) {
Map perms = zPermissionsService.getPlayerPermissions(null, null, name);
- if (perms.containsKey(node.getNode()))
+ if (perms.containsKey(node.getNode())) {
return perms.get(node.getNode());
- else
+ } else {
return false;
- }
-
- @Override
- public boolean isInGroup(Player player, String group) {
- return getGroups(player).contains(group);
+ }
}
@Override
public boolean removeFromGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player " + player.getName() + " removegroup " + group);
}
@Override
public boolean setGroup(Player player, String group) {
- return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + group);
+ return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
+ "permissions player " + player.getName() + " setgroup " + group);
}
@Override
- public List getGroups(Player player) {
+ public Collection getGroups(Player player) {
// TODO Gnat008 20160631: Use UUID not name?
- return new ArrayList(zPermissionsService.getPlayerGroups(player.getName()));
+ return zPermissionsService.getPlayerGroups(player.getName());
}
@Override
diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java
index 8aea8341a..fee949574 100644
--- a/src/main/java/fr/xephi/authme/process/Management.java
+++ b/src/main/java/fr/xephi/authme/process/Management.java
@@ -8,7 +8,8 @@ import fr.xephi.authme.process.login.AsynchronousLogin;
import fr.xephi.authme.process.logout.AsynchronousLogout;
import fr.xephi.authme.process.quit.AsynchronousQuit;
import fr.xephi.authme.process.register.AsyncRegister;
-import fr.xephi.authme.process.register.executors.RegistrationExecutor;
+import fr.xephi.authme.process.register.executors.RegistrationMethod;
+import fr.xephi.authme.process.register.executors.RegistrationParameters;
import fr.xephi.authme.process.unregister.AsynchronousUnregister;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.command.CommandSender;
@@ -60,8 +61,8 @@ public class Management {
runTask(() -> asynchronousLogout.logout(player));
}
- public void performRegister(Player player, RegistrationExecutor registrationExecutor) {
- runTask(() -> asyncRegister.register(player, registrationExecutor));
+ public void performRegister(RegistrationMethod
variant, P parameters) {
+ runTask(() -> asyncRegister.register(variant, parameters));
}
public void performUnregister(Player player, String password) {
diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
index dab9cdc2c..53f4d8fd0 100644
--- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
+++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
@@ -1,37 +1,37 @@
package fr.xephi.authme.process.join;
-import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.SessionManager;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.ProtectInventoryEvent;
-import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.login.AsynchronousLogin;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.PluginHookService;
+import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
-import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.GameMode;
+import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import javax.inject.Inject;
-import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
+import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
/**
* Asynchronous process for when a player joins.
@@ -39,7 +39,7 @@ import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
public class AsynchronousJoin implements AsynchronousProcess {
@Inject
- private AuthMe plugin;
+ private Server server;
@Inject
private DataSource database;
@@ -51,7 +51,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
private PlayerCache playerCache;
@Inject
- private LimboCache limboCache;
+ private LimboService limboService;
@Inject
private SessionManager sessionManager;
@@ -62,15 +62,15 @@ public class AsynchronousJoin implements AsynchronousProcess {
@Inject
private BukkitService bukkitService;
- @Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
-
@Inject
private AsynchronousLogin asynchronousLogin;
@Inject
private CommandManager commandManager;
+ @Inject
+ private ValidationService validationService;
+
AsynchronousJoin() {
}
@@ -91,13 +91,13 @@ public class AsynchronousJoin implements AsynchronousProcess {
pluginHookService.setEssentialsSocialSpyStatus(player, false);
}
- if (isNameRestricted(name, ip, player.getAddress().getHostName())) {
+ if (!validationService.fulfillsNameRestrictions(player)) {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
@Override
public void run() {
player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR));
if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) {
- plugin.getServer().banIP(ip);
+ server.banIP(ip);
}
}
});
@@ -111,8 +111,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
final boolean isAuthAvailable = database.isAuthAvailable(name);
if (isAuthAvailable) {
- limboCache.addPlayerData(player);
- service.setGroup(player, AuthGroupType.NOT_LOGGED_IN);
+ service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
// Protect inventory
if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
@@ -130,19 +129,17 @@ public class AsynchronousJoin implements AsynchronousProcess {
PlayerAuth auth = database.getAuth(name);
database.setUnlogged(name);
playerCache.removePlayer(name);
- if (auth != null && auth.getIp().equals(ip)) {
- service.send(player, MessageKey.SESSION_RECONNECTION);
- bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player));
- return;
- } else if (service.getProperty(PluginSettings.SESSIONS_EXPIRE_ON_IP_CHANGE)) {
- service.send(player, MessageKey.SESSION_EXPIRED);
+ if (auth != null) {
+ if (auth.getIp().equals(ip)) {
+ service.send(player, MessageKey.SESSION_RECONNECTION);
+ bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player));
+ return;
+ } else {
+ service.send(player, MessageKey.SESSION_EXPIRED);
+ }
}
}
} else {
- // Not Registered. Delete old data, load default one.
- limboCache.deletePlayerData(player);
- limboCache.addPlayerData(player);
-
// Groups logic
service.setGroup(player, AuthGroupType.UNREGISTERED);
@@ -155,12 +152,8 @@ public class AsynchronousJoin implements AsynchronousProcess {
final int registrationTimeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
- player.setOp(false);
- if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)
- && service.getProperty(RestrictionSettings.REMOVE_SPEED)) {
- player.setFlySpeed(0.0f);
- player.setWalkSpeed(0.0f);
- }
+ limboService.createLimboPlayer(player, isAuthAvailable);
+
player.setNoDamageTicks(registrationTimeout);
if (pluginHookService.isEssentialsAvailable() && service.getProperty(HooksSettings.USE_ESSENTIALS_MOTD)) {
player.performCommand("motd");
@@ -172,40 +165,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
}
commandManager.runCommandsOnJoin(player);
});
-
- // Timeout and message task
- limboPlayerTaskManager.registerTimeoutTask(player);
- limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable);
- }
-
- /**
- * Returns whether the name is restricted based on the restriction settings.
- *
- * @param name The name to check
- * @param ip The IP address of the player
- * @param domain The hostname of the IP address
- *
- * @return True if the name is restricted (IP/domain is not allowed for the given name),
- * false if the restrictions are met or if the name has no restrictions to it
- */
- private boolean isNameRestricted(String name, String ip, String domain) {
- if (!service.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)) {
- return false;
- }
-
- boolean nameFound = false;
- for (String entry : service.getProperty(RestrictionSettings.ALLOWED_RESTRICTED_USERS)) {
- String[] args = entry.split(";");
- String testName = args[0];
- String testIp = args[1];
- if (testName.equalsIgnoreCase(name)) {
- nameFound = true;
- if ((ip != null && testIp.equals(ip)) || (domain != null && testIp.equalsIgnoreCase(domain))) {
- return false;
- }
- }
- }
- return nameFound;
}
/**
diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
index db85b8581..fa0c76ab1 100644
--- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
@@ -6,26 +6,24 @@ import fr.xephi.authme.data.CaptchaManager;
import fr.xephi.authme.data.TempbanManager;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.data.limbo.LimboCache;
-import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
+import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AdminPermission;
-import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SyncProcessManager;
import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
-import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
@@ -46,15 +44,9 @@ public class AsynchronousLogin implements AsynchronousProcess {
@Inject
private CommonService service;
- @Inject
- private PermissionsManager permissionsManager;
-
@Inject
private PlayerCache playerCache;
- @Inject
- private LimboCache limboCache;
-
@Inject
private SyncProcessManager syncProcessManager;
@@ -71,7 +63,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
private TempbanManager tempbanManager;
@Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
+ private LimboService limboService;
+
+ @Inject
+ private EmailService emailService;
AsynchronousLogin() {
}
@@ -105,6 +100,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
* Checks the precondition for authentication (like user known) and returns
* the player's {@link PlayerAuth} object.
*
+ * @param player the player to check
* @return the PlayerAuth object, or {@code null} if the player doesn't exist or may not log in
* (e.g. because he is already logged in)
*/
@@ -119,8 +115,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
if (auth == null) {
service.send(player, MessageKey.UNKNOWN_USER);
// Recreate the message task to immediately send the message again as response
- // and to make sure we send the right register message (password vs. email registration)
- limboPlayerTaskManager.registerMessageTask(name, false);
+ limboService.resetMessageTask(player, false);
return null;
}
@@ -195,9 +190,11 @@ public class AsynchronousLogin implements AsynchronousProcess {
// If the authentication fails check if Captcha is required and send a message to the player
if (captchaManager.isCaptchaRequired(player.getName())) {
- limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(true);
+ limboService.muteMessageTask(player);
service.send(player, MessageKey.USAGE_CAPTCHA,
captchaManager.getCaptchaCodeOrGenerateNew(player.getName()));
+ } else if (emailService.hasAllInformation()) {
+ service.send(player, MessageKey.FORGOT_PASSWORD_MESSAGE);
}
}
}
@@ -246,10 +243,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
// task, we schedule it in the end
// so that we can be sure, and have not to care if it might be
// processed in other order.
- LimboPlayer limboPlayer = limboCache.getPlayerData(name);
- if (limboPlayer != null) {
- limboPlayer.clearTasks();
- }
syncProcessManager.processSyncPlayerLogin(player);
} else {
ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted...");
@@ -260,7 +253,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
int threshold = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD_THRESHOLD);
String command = service.getProperty(RestrictionSettings.OTHER_ACCOUNTS_CMD);
- if(threshold < 2 || command.isEmpty()) {
+ if (threshold < 2 || command.isEmpty()) {
return;
}
@@ -269,17 +262,13 @@ public class AsynchronousLogin implements AsynchronousProcess {
}
bukkitService.dispatchConsoleCommand(command
- .replaceAll("%playername%", player.getName())
- .replaceAll("%playerip%", ip)
+ .replace("%playername%", player.getName())
+ .replace("%playerip%", ip)
);
}
private void displayOtherAccounts(List auths, Player player) {
- if (!service.getProperty(RestrictionSettings.DISPLAY_OTHER_ACCOUNTS)) {
- return;
- }
-
- if (auths.size() <= 1) {
+ if (!service.getProperty(RestrictionSettings.DISPLAY_OTHER_ACCOUNTS) || auths.size() <= 1) {
return;
}
@@ -300,10 +289,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
for (Player onlinePlayer : bukkitService.getOnlinePlayers()) {
if (onlinePlayer.getName().equalsIgnoreCase(player.getName())
- && permissionsManager.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) {
+ && service.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) {
service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_SELF, Integer.toString(auths.size()));
onlinePlayer.sendMessage(message);
- } else if (permissionsManager.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) {
+ } else if (service.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) {
service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER,
player.getName(), Integer.toString(auths.size()));
onlinePlayer.sendMessage(message);
@@ -323,7 +312,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) {
// Do not perform the check if player has multiple accounts permission or if IP is localhost
if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0
- || permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
+ || service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|| "127.0.0.1".equalsIgnoreCase(ip)
|| "localhost".equalsIgnoreCase(ip)) {
return false;
diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
index d64bda776..1e4965d5e 100644
--- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
@@ -1,47 +1,42 @@
package fr.xephi.authme.process.login;
-import fr.xephi.authme.AuthMe;
import fr.xephi.authme.data.auth.PlayerAuth;
-import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.LoginEvent;
import fr.xephi.authme.events.RestoreInventoryEvent;
import fr.xephi.authme.listener.PlayerListener;
+import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.BungeeService;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.JoinMessageService;
import fr.xephi.authme.service.TeleportationService;
-import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.WelcomeMessageConfiguration;
import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.util.StringUtils;
-import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
-import org.bukkit.plugin.PluginManager;
import org.bukkit.potion.PotionEffectType;
import javax.inject.Inject;
+import java.util.List;
import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
public class ProcessSyncPlayerLogin implements SynchronousProcess {
- @Inject
- private AuthMe plugin;
-
@Inject
private BungeeService bungeeService;
@Inject
- private LimboCache limboCache;
+ private LimboService limboService;
@Inject
private BukkitService bukkitService;
- @Inject
- private PluginManager pluginManager;
-
@Inject
private TeleportationService teleportationService;
@@ -52,14 +47,20 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
private CommandManager commandManager;
@Inject
- private Settings settings;
+ private CommonService commonService;
+
+ @Inject
+ private WelcomeMessageConfiguration welcomeMessageConfiguration;
+
+ @Inject
+ private JoinMessageService joinMessageService;
ProcessSyncPlayerLogin() {
}
private void restoreInventory(Player player) {
RestoreInventoryEvent event = new RestoreInventoryEvent(player);
- pluginManager.callEvent(event);
+ bukkitService.callEvent(event);
if (!event.isCancelled()) {
player.updateInventory();
}
@@ -68,16 +69,14 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
public void processPlayerLogin(Player player) {
final String name = player.getName().toLowerCase();
- final LimboPlayer limbo = limboCache.getPlayerData(name);
+ commonService.setGroup(player, AuthGroupType.LOGGED_IN);
+ final LimboPlayer limbo = limboService.getLimboPlayer(name);
// Limbo contains the State of the Player before /login
if (limbo != null) {
- limboCache.restoreData(player);
- limboCache.deletePlayerData(player);
- // do we really need to use location from database for now?
- // because LimboCache#restoreData teleport player to last location.
+ limboService.restoreData(player);
}
- if (settings.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
+ if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
restoreInventory(player);
}
@@ -85,16 +84,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
teleportationService.teleportOnLogin(player, auth, limbo);
// We can now display the join message (if delayed)
- String joinMessage = PlayerListener.joinMessage.remove(name);
- if (!StringUtils.isEmpty(joinMessage)) {
- for (Player p : bukkitService.getOnlinePlayers()) {
- if (p.isOnline()) {
- p.sendMessage(joinMessage);
- }
- }
- }
+ joinMessageService.sendMessage(name);
- if (settings.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
+ if (commonService.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
player.removePotionEffect(PotionEffectType.BLINDNESS);
}
@@ -103,15 +95,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
player.saveData();
// Login is done, display welcome message
- if (settings.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) {
- if (settings.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) {
- for (String s : settings.getWelcomeMessage()) {
- Bukkit.getServer().broadcastMessage(plugin.replaceAllInfo(s, player));
- }
+ List welcomeMessage = welcomeMessageConfiguration.getWelcomeMessage(player);
+ if (commonService.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) {
+ if (commonService.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) {
+ welcomeMessage.forEach(bukkitService::broadcastMessage);
} else {
- for (String s : settings.getWelcomeMessage()) {
- player.sendMessage(plugin.replaceAllInfo(s, player));
- }
+ welcomeMessage.forEach(player::sendMessage);
}
}
diff --git a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java
index efd3066aa..1f59d9657 100644
--- a/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java
+++ b/src/main/java/fr/xephi/authme/process/logout/AsynchronousLogout.java
@@ -2,12 +2,11 @@ package fr.xephi.authme.process.logout;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.AsynchronousProcess;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SyncProcessManager;
+import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.entity.Player;
@@ -24,9 +23,6 @@ public class AsynchronousLogout implements AsynchronousProcess {
@Inject
private PlayerCache playerCache;
- @Inject
- private LimboCache limboCache;
-
@Inject
private SyncProcessManager syncProcessManager;
@@ -47,7 +43,6 @@ public class AsynchronousLogout implements AsynchronousProcess {
database.updateQuitLoc(auth);
}
- limboCache.addPlayerData(player);
playerCache.removePlayer(name);
database.setUnlogged(name);
syncProcessManager.processSyncPlayerLogout(player);
diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
index 17710df45..028781587 100644
--- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
+++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
@@ -2,17 +2,17 @@ package fr.xephi.authme.process.logout;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.SessionManager;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.events.LogoutEvent;
import fr.xephi.authme.listener.protocollib.ProtocolLibService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.TeleportationService;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.service.TeleportationService;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
@@ -34,7 +34,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess {
private ProtocolLibService protocolLibService;
@Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
+ private LimboService limboService;
@Inject
private SessionManager sessionManager;
@@ -53,9 +53,6 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess {
protocolLibService.sendBlankInventoryPacket(player);
}
- limboPlayerTaskManager.registerTimeoutTask(player);
- limboPlayerTaskManager.registerMessageTask(name, true);
-
applyLogoutEffect(player);
// Player is now logout... Time to fire event !
@@ -71,21 +68,14 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess {
teleportationService.teleportOnJoin(player);
// Apply Blindness effect
- final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
+ int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2));
}
// Set player's data to unauthenticated
- service.setGroup(player, AuthGroupType.NOT_LOGGED_IN);
- player.setOp(false);
- player.setAllowFlight(false);
- // Remove speed
- if (!service.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)
- && service.getProperty(RestrictionSettings.REMOVE_SPEED)) {
- player.setFlySpeed(0.0f);
- player.setWalkSpeed(0.0f);
- }
+ limboService.createLimboPlayer(player, true);
+ service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
}
}
diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java
index 73db67f89..c5f633e96 100644
--- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java
+++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java
@@ -1,7 +1,6 @@
package fr.xephi.authme.process.quit;
-import fr.xephi.authme.data.backup.LimboPlayerStorage;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.process.SynchronousProcess;
import org.bukkit.entity.Player;
@@ -11,22 +10,10 @@ import javax.inject.Inject;
public class ProcessSyncronousPlayerQuit implements SynchronousProcess {
@Inject
- private LimboPlayerStorage limboPlayerStorage;
-
- @Inject
- private LimboCache limboCache;
+ private LimboService limboService;
public void processSyncQuit(Player player) {
- if (limboCache.hasPlayerData(player.getName())) { // it mean player is not authenticated
- limboCache.restoreData(player);
- limboCache.removeFromCache(player);
- } else {
- // Save player's data, so we could retrieve it later on player next join
- if (!limboPlayerStorage.hasData(player)) {
- limboPlayerStorage.saveData(player);
- }
- }
-
+ limboService.restoreData(player);
player.leaveVehicle();
}
}
diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
index d32f6eadb..83e5a82cb 100644
--- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
@@ -3,10 +3,13 @@ package fr.xephi.authme.process.register;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.AsynchronousProcess;
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
+import fr.xephi.authme.process.register.executors.RegistrationParameters;
+import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
@@ -31,6 +34,8 @@ public class AsyncRegister implements AsynchronousProcess {
private CommonService service;
@Inject
private PermissionsManager permissionsManager;
+ @Inject
+ private SingletonStore registrationExecutorFactory;
AsyncRegister() {
}
@@ -38,12 +43,16 @@ public class AsyncRegister implements AsynchronousProcess {
/**
* Performs the registration process for the given player.
*
- * @param player the player to register
- * @param executor the registration executor to perform the registration with
+ * @param variant the registration method
+ * @param parameters the parameters
+ * @param parameters type
*/
- public void register(Player player, RegistrationExecutor executor) {
- if (preRegisterCheck(player) && executor.isRegistrationAdmitted()) {
- executeRegistration(player, executor);
+ public
void register(RegistrationMethod
variant, P parameters) {
+ if (preRegisterCheck(parameters.getPlayer())) {
+ RegistrationExecutor
executor = registrationExecutorFactory.getSingleton(variant.getExecutorClass());
+ if (executor.isRegistrationAdmitted(parameters)) {
+ executeRegistration(parameters, executor);
+ }
}
}
@@ -66,15 +75,17 @@ public class AsyncRegister implements AsynchronousProcess {
/**
* Executes the registration.
*
- * @param player the player to register
+ * @param parameters the registration parameters
* @param executor the executor to perform the registration process with
+ * @param
registration params type
*/
- private void executeRegistration(Player player, RegistrationExecutor executor) {
- PlayerAuth auth = executor.buildPlayerAuth();
+ private
+ void executeRegistration(P parameters, RegistrationExecutor
executor) {
+ PlayerAuth auth = executor.buildPlayerAuth(parameters);
if (database.saveAuth(auth)) {
- executor.executePostPersistAction();
+ executor.executePostPersistAction(parameters);
} else {
- service.send(player, MessageKey.ERROR);
+ service.send(parameters.getPlayer(), MessageKey.ERROR);
}
}
diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
index aea1b4be8..4e57b973b 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
@@ -1,12 +1,11 @@
package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess;
-import fr.xephi.authme.settings.properties.HooksSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
+import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
@@ -19,20 +18,16 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
private CommonService service;
@Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
+ private LimboService limboService;
ProcessSyncEmailRegister() {
}
public void processEmailRegister(Player player) {
- final String name = player.getName().toLowerCase();
- if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) {
- service.setGroup(player, AuthGroupType.REGISTERED);
- }
+ service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED);
- limboPlayerTaskManager.registerTimeoutTask(player);
- limboPlayerTaskManager.registerMessageTask(name, true);
+ limboService.replaceTasksAfterRegistration(player);
player.saveData();
ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player));
diff --git a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
index fae428d00..173cc0d82 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
@@ -1,17 +1,15 @@
package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.service.BungeeService;
+import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.EmailSettings;
-import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
@@ -28,10 +26,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
private CommonService service;
@Inject
- private LimboCache limboCache;
-
- @Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
+ private LimboService limboService;
@Inject
private CommandManager commandManager;
@@ -45,10 +40,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
* @param player the player
*/
private void requestLogin(Player player) {
- final String name = player.getName().toLowerCase();
- limboCache.updatePlayerData(player);
- limboPlayerTaskManager.registerTimeoutTask(player);
- limboPlayerTaskManager.registerMessageTask(name, true);
+ limboService.replaceTasksAfterRegistration(player);
if (player.isInsideVehicle() && player.getVehicle() != null) {
player.getVehicle().eject();
@@ -56,10 +48,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
}
public void processPasswordRegister(Player player) {
- if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) {
- service.setGroup(player, AuthGroupType.REGISTERED);
- }
-
+ service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
service.send(player, MessageKey.REGISTER_SUCCESS);
if (!service.getProperty(EmailSettings.MAIL_ACCOUNT).isEmpty()) {
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java
new file mode 100644
index 000000000..179aa59df
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterExecutor.java
@@ -0,0 +1,99 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.process.SyncProcessManager;
+import fr.xephi.authme.process.login.AsynchronousLogin;
+import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.ValidationService;
+import fr.xephi.authme.settings.properties.PluginSettings;
+import fr.xephi.authme.settings.properties.RegistrationSettings;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+
+/**
+ * Registration executor for registration methods where the password
+ * is supplied by the user.
+ *
+ * @param
the parameters type
+ */
+abstract class AbstractPasswordRegisterExecutor
+ implements RegistrationExecutor
{
+
+ /**
+ * Number of ticks to wait before running the login action when it is run synchronously.
+ * A small delay is necessary or the database won't return the newly saved PlayerAuth object
+ * and the login process thinks the user is not registered.
+ */
+ private static final int SYNC_LOGIN_DELAY = 5;
+
+ @Inject
+ private ValidationService validationService;
+
+ @Inject
+ private CommonService commonService;
+
+ @Inject
+ private PasswordSecurity passwordSecurity;
+
+ @Inject
+ private BukkitService bukkitService;
+
+ @Inject
+ private SyncProcessManager syncProcessManager;
+
+ @Inject
+ private AsynchronousLogin asynchronousLogin;
+
+ @Override
+ public boolean isRegistrationAdmitted(P params) {
+ ValidationService.ValidationResult passwordValidation = validationService.validatePassword(
+ params.getPassword(), params.getPlayer().getName());
+ if (passwordValidation.hasError()) {
+ commonService.send(params.getPlayer(), passwordValidation.getMessageKey(), passwordValidation.getArgs());
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public PlayerAuth buildPlayerAuth(P params) {
+ HashedPassword hashedPassword = passwordSecurity.computeHash(params.getPassword(), params.getPlayerName());
+ params.setHashedPassword(hashedPassword);
+ return createPlayerAuthObject(params);
+ }
+
+ /**
+ * Creates the PlayerAuth object to store into the database, based on the registration parameters.
+ *
+ * @param params the parameters
+ * @return the PlayerAuth representing the new account to register
+ */
+ protected abstract PlayerAuth createPlayerAuthObject(P params);
+
+ /**
+ * Returns whether the player should be automatically logged in after registration.
+ *
+ * @param params the registration parameters
+ * @return true if the player should be logged in, false otherwise
+ */
+ protected boolean performLoginAfterRegister(P params) {
+ return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER);
+ }
+
+ @Override
+ public void executePostPersistAction(P params) {
+ final Player player = params.getPlayer();
+ if (performLoginAfterRegister(params)) {
+ if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) {
+ bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player));
+ } else {
+ bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY);
+ }
+ }
+ syncProcessManager.processSyncPasswordRegister(player);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java
new file mode 100644
index 000000000..0c7a1d515
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/AbstractPasswordRegisterParams.java
@@ -0,0 +1,48 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.security.crypts.HashedPassword;
+import org.bukkit.entity.Player;
+
+/**
+ * Common params type for implementors of {@link AbstractPasswordRegisterExecutor}.
+ * Password must be supplied on creation and cannot be changed later on. The {@link HashedPassword}
+ * is stored on the params object for later use.
+ */
+public abstract class AbstractPasswordRegisterParams extends RegistrationParameters {
+
+ private final String password;
+ private HashedPassword hashedPassword;
+
+ /**
+ * Constructor.
+ *
+ * @param player the player to register
+ * @param password the password to use
+ */
+ public AbstractPasswordRegisterParams(Player player, String password) {
+ super(player);
+ this.password = password;
+ }
+
+ /**
+ * Constructor with no defined password. Use for registration methods which
+ * have no implicit password (like two factor authentication).
+ *
+ * @param player the player to register
+ */
+ public AbstractPasswordRegisterParams(Player player) {
+ this(player, null);
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ void setHashedPassword(HashedPassword hashedPassword) {
+ this.hashedPassword = hashedPassword;
+ }
+
+ HashedPassword getHashedPassword() {
+ return hashedPassword;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java
new file mode 100644
index 000000000..6005c8e41
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterExecutor.java
@@ -0,0 +1,20 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+
+/**
+ * Executor for password registration via API call.
+ */
+class ApiPasswordRegisterExecutor extends AbstractPasswordRegisterExecutor {
+
+ @Override
+ protected PlayerAuth createPlayerAuthObject(ApiPasswordRegisterParams params) {
+ return PlayerAuthBuilderHelper
+ .createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null);
+ }
+
+ @Override
+ protected boolean performLoginAfterRegister(ApiPasswordRegisterParams params) {
+ return params.getLoginAfterRegister();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java
new file mode 100644
index 000000000..357d32c22
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/ApiPasswordRegisterParams.java
@@ -0,0 +1,35 @@
+package fr.xephi.authme.process.register.executors;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Parameters for {@link ApiPasswordRegisterExecutor}.
+ */
+public class ApiPasswordRegisterParams extends PasswordRegisterParams {
+
+ private final boolean loginAfterRegister;
+
+ protected ApiPasswordRegisterParams(Player player, String password, boolean loginAfterRegister) {
+ super(player, password, null);
+ this.loginAfterRegister = loginAfterRegister;
+ }
+
+ /**
+ * Creates a parameters object.
+ *
+ * @param player the player to register
+ * @param password the password to register with
+ * @param loginAfterRegister whether the player should be logged in after registration
+ * @return params object with the given data
+ */
+ public static ApiPasswordRegisterParams of(Player player, String password, boolean loginAfterRegister) {
+ return new ApiPasswordRegisterParams(player, password, loginAfterRegister);
+ }
+
+ /**
+ * @return true if the player should be logged in after being registered, false otherwise
+ */
+ public boolean getLoginAfterRegister() {
+ return loginAfterRegister;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java
new file mode 100644
index 000000000..0f4153b18
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutor.java
@@ -0,0 +1,80 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.mail.EmailService;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.permission.PermissionsManager;
+import fr.xephi.authme.process.SyncProcessManager;
+import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.settings.properties.EmailSettings;
+import fr.xephi.authme.util.RandomStringUtils;
+import org.bukkit.entity.Player;
+
+import javax.inject.Inject;
+
+import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS;
+import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
+import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
+
+/**
+ * Executor for email registration: the player only provides his email address,
+ * to which a generated password is sent.
+ */
+class EmailRegisterExecutor implements RegistrationExecutor {
+
+ @Inject
+ private PermissionsManager permissionsManager;
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private CommonService commonService;
+
+ @Inject
+ private EmailService emailService;
+
+ @Inject
+ private SyncProcessManager syncProcessManager;
+
+ @Inject
+ private PasswordSecurity passwordSecurity;
+
+ @Override
+ public boolean isRegistrationAdmitted(EmailRegisterParams params) {
+ final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL);
+ if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(params.getPlayer(), ALLOW_MULTIPLE_ACCOUNTS)) {
+ int otherAccounts = dataSource.countAuthsByEmail(params.getEmail());
+ if (otherAccounts >= maxRegPerEmail) {
+ commonService.send(params.getPlayer(), MessageKey.MAX_REGISTER_EXCEEDED,
+ Integer.toString(maxRegPerEmail), Integer.toString(otherAccounts), "@");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public PlayerAuth buildPlayerAuth(EmailRegisterParams params) {
+ String password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
+ HashedPassword hashedPassword = passwordSecurity.computeHash(password, params.getPlayer().getName());
+ params.setPassword(password);
+ return createPlayerAuth(params.getPlayer(), hashedPassword, params.getEmail());
+ }
+
+ @Override
+ public void executePostPersistAction(EmailRegisterParams params) {
+ Player player = params.getPlayer();
+ boolean couldSendMail = emailService.sendPasswordMail(
+ player.getName(), params.getEmail(), params.getPassword());
+ if (couldSendMail) {
+ syncProcessManager.processSyncEmailRegister(player);
+ } else {
+ commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
+ }
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java
deleted file mode 100644
index 88f8cd602..000000000
--- a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package fr.xephi.authme.process.register.executors;
-
-import fr.xephi.authme.data.auth.PlayerAuth;
-import fr.xephi.authme.datasource.DataSource;
-import fr.xephi.authme.mail.SendMailSSL;
-import fr.xephi.authme.message.MessageKey;
-import fr.xephi.authme.permission.PermissionsManager;
-import fr.xephi.authme.process.SyncProcessManager;
-import fr.xephi.authme.security.PasswordSecurity;
-import fr.xephi.authme.security.crypts.HashedPassword;
-import fr.xephi.authme.service.CommonService;
-import fr.xephi.authme.settings.properties.EmailSettings;
-import fr.xephi.authme.util.RandomStringUtils;
-import org.bukkit.entity.Player;
-
-import javax.inject.Inject;
-
-import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
-import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS;
-import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
-
-/**
- * Provides a registration executor for email registration.
- */
-class EmailRegisterExecutorProvider {
-
- @Inject
- private PermissionsManager permissionsManager;
-
- @Inject
- private DataSource dataSource;
-
- @Inject
- private CommonService commonService;
-
- @Inject
- private SendMailSSL sendMailSsl;
-
- @Inject
- private SyncProcessManager syncProcessManager;
-
- @Inject
- private PasswordSecurity passwordSecurity;
-
- EmailRegisterExecutorProvider() {
- }
-
- /** Registration executor implementation for email registration. */
- class EmailRegisterExecutor implements RegistrationExecutor {
-
- private final Player player;
- private final String email;
- private String password;
-
- EmailRegisterExecutor(Player player, String email) {
- this.player = player;
- this.email = email;
- }
-
- @Override
- public boolean isRegistrationAdmitted() {
- final int maxRegPerEmail = commonService.getProperty(EmailSettings.MAX_REG_PER_EMAIL);
- if (maxRegPerEmail > 0 && !permissionsManager.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) {
- int otherAccounts = dataSource.countAuthsByEmail(email);
- if (otherAccounts >= maxRegPerEmail) {
- commonService.send(player, MessageKey.MAX_REGISTER_EXCEEDED, Integer.toString(maxRegPerEmail),
- Integer.toString(otherAccounts), "@");
- return false;
- }
- }
- return true;
- }
-
- @Override
- public PlayerAuth buildPlayerAuth() {
- password = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
- HashedPassword hashedPassword = passwordSecurity.computeHash(password, player.getName());
- return createPlayerAuth(player, hashedPassword, email);
- }
-
- @Override
- public void executePostPersistAction() {
- boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password);
- if (couldSendMail) {
- syncProcessManager.processSyncEmailRegister(player);
- } else {
- commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
- }
- }
- }
-}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java
new file mode 100644
index 000000000..94d03acc5
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterParams.java
@@ -0,0 +1,43 @@
+package fr.xephi.authme.process.register.executors;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Parameters for email registration.
+ */
+public class EmailRegisterParams extends RegistrationParameters {
+
+ private final String email;
+ private String password;
+
+ protected EmailRegisterParams(Player player, String email) {
+ super(player);
+ this.email = email;
+ }
+
+ /**
+ * Creates a params object for email registration.
+ *
+ * @param player the player to register
+ * @param email the player's email
+ * @return params object with the given data
+ */
+ public static EmailRegisterParams of(Player player, String email) {
+ return new EmailRegisterParams(player, email);
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * @return the password generated for the player
+ */
+ String getPassword() {
+ return password;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java
new file mode 100644
index 000000000..167bee292
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutor.java
@@ -0,0 +1,17 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+
+import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
+
+/**
+ * Registration executor for password registration.
+ */
+class PasswordRegisterExecutor extends AbstractPasswordRegisterExecutor {
+
+ @Override
+ public PlayerAuth createPlayerAuthObject(PasswordRegisterParams params) {
+ return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), params.getEmail());
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java
deleted file mode 100644
index a79c63c82..000000000
--- a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package fr.xephi.authme.process.register.executors;
-
-import fr.xephi.authme.data.auth.PlayerAuth;
-import fr.xephi.authme.message.MessageKey;
-import fr.xephi.authme.process.SyncProcessManager;
-import fr.xephi.authme.process.login.AsynchronousLogin;
-import fr.xephi.authme.security.PasswordSecurity;
-import fr.xephi.authme.security.crypts.HashedPassword;
-import fr.xephi.authme.security.crypts.TwoFactor;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.service.CommonService;
-import fr.xephi.authme.service.ValidationService;
-import fr.xephi.authme.service.ValidationService.ValidationResult;
-import fr.xephi.authme.settings.properties.PluginSettings;
-import fr.xephi.authme.settings.properties.RegistrationSettings;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-
-import javax.inject.Inject;
-
-import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
-
-/**
- * Provides registration executors for password-based registration variants.
- */
-class PasswordRegisterExecutorProvider {
-
- /**
- * Number of ticks to wait before running the login action when it is run synchronously.
- * A small delay is necessary or the database won't return the newly saved PlayerAuth object
- * and the login process thinks the user is not registered.
- */
- private static final int SYNC_LOGIN_DELAY = 5;
-
- @Inject
- private ValidationService validationService;
-
- @Inject
- private CommonService commonService;
-
- @Inject
- private PasswordSecurity passwordSecurity;
-
- @Inject
- private BukkitService bukkitService;
-
- @Inject
- private SyncProcessManager syncProcessManager;
-
- @Inject
- private AsynchronousLogin asynchronousLogin;
-
- PasswordRegisterExecutorProvider() {
- }
-
- /** Registration executor for password registration. */
- class PasswordRegisterExecutor implements RegistrationExecutor {
-
- protected final Player player;
- private final String password;
- private final String email;
- protected HashedPassword hashedPassword;
-
- /**
- * Constructor.
- *
- * @param player the player to register
- * @param password the password to register with
- * @param email the email of the player (may be null)
- */
- PasswordRegisterExecutor(Player player, String password, String email) {
- this.player = player;
- this.password = password;
- this.email = email;
- }
-
- @Override
- public boolean isRegistrationAdmitted() {
- ValidationResult passwordValidation = validationService.validatePassword(password, player.getName());
- if (passwordValidation.hasError()) {
- commonService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs());
- return false;
- }
- return true;
- }
-
- @Override
- public PlayerAuth buildPlayerAuth() {
- hashedPassword = passwordSecurity.computeHash(password, player.getName().toLowerCase());
- return createPlayerAuth(player, hashedPassword, email);
- }
-
- protected boolean performLoginAfterRegister() {
- return !commonService.getProperty(RegistrationSettings.FORCE_LOGIN_AFTER_REGISTER);
- }
-
- @Override
- public void executePostPersistAction() {
- if (performLoginAfterRegister()) {
- if (commonService.getProperty(PluginSettings.USE_ASYNC_TASKS)) {
- bukkitService.runTaskAsynchronously(() -> asynchronousLogin.forceLogin(player));
- } else {
- bukkitService.scheduleSyncDelayedTask(() -> asynchronousLogin.forceLogin(player), SYNC_LOGIN_DELAY);
- }
- }
- syncProcessManager.processSyncPasswordRegister(player);
- }
- }
-
- /** Executor for password registration via API call. */
- class ApiPasswordRegisterExecutor extends PasswordRegisterExecutor {
-
- private final boolean loginAfterRegister;
-
- /**
- * Constructor.
- *
- * @param player the player to register
- * @param password the password to register with
- * @param loginAfterRegister whether the user should be automatically logged in after registration
- */
- ApiPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) {
- super(player, password, null);
- this.loginAfterRegister = loginAfterRegister;
- }
-
- @Override
- protected boolean performLoginAfterRegister() {
- return loginAfterRegister;
- }
- }
-
- /** Executor for two factor registration. */
- class TwoFactorRegisterExecutor extends PasswordRegisterExecutor {
-
- TwoFactorRegisterExecutor(Player player) {
- super(player, "", null);
- }
-
- @Override
- public boolean isRegistrationAdmitted() {
- // nothing to check
- return true;
- }
-
- @Override
- public void executePostPersistAction() {
- super.executePostPersistAction();
-
- String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash());
- commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl);
- }
-
- }
-}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java
new file mode 100644
index 000000000..f21861bff
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterParams.java
@@ -0,0 +1,32 @@
+package fr.xephi.authme.process.register.executors;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Parameters for registration with a given password, and optionally an email address.
+ */
+public class PasswordRegisterParams extends AbstractPasswordRegisterParams {
+
+ private final String email;
+
+ protected PasswordRegisterParams(Player player, String password, String email) {
+ super(player, password);
+ this.email = email;
+ }
+
+ /**
+ * Creates a params object.
+ *
+ * @param player the player to register
+ * @param password the password to register with
+ * @param email the email of the player (may be null)
+ * @return params object with the given data
+ */
+ public static PasswordRegisterParams of(Player player, String password, String email) {
+ return new PasswordRegisterParams(player, password, email);
+ }
+
+ public String getEmail() {
+ return email;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java
index 1943b5d54..5d9ab8657 100644
--- a/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java
+++ b/src/main/java/fr/xephi/authme/process/register/executors/PlayerAuthBuilderHelper.java
@@ -13,6 +13,14 @@ final class PlayerAuthBuilderHelper {
private PlayerAuthBuilderHelper() {
}
+ /**
+ * Creates a {@link PlayerAuth} object with the given data.
+ *
+ * @param player the player to create a PlayerAuth for
+ * @param hashedPassword the hashed password
+ * @param email the email address (nullable)
+ * @return the generated PlayerAuth object
+ */
static PlayerAuth createPlayerAuth(Player player, HashedPassword hashedPassword, String email) {
return PlayerAuth.builder()
.name(player.getName().toLowerCase())
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java
index cceb5c18e..32bfd9517 100644
--- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java
+++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutor.java
@@ -4,8 +4,10 @@ import fr.xephi.authme.data.auth.PlayerAuth;
/**
* Performs the registration action.
+ *
+ * @param the registration parameters type
*/
-public interface RegistrationExecutor {
+public interface RegistrationExecutor
{
/**
* Returns whether the registration may take place. Use this method to execute
@@ -14,20 +16,24 @@ public interface RegistrationExecutor {
* If this method returns {@code false}, it is expected that the executor inform
* the player about the error within this method call.
*
+ * @param params the parameters for the registration
* @return true if registration may be performed, false otherwise
*/
- boolean isRegistrationAdmitted();
+ boolean isRegistrationAdmitted(P params);
/**
* Constructs the PlayerAuth object to persist into the database.
*
+ * @param params the parameters for the registration
* @return the player auth to register in the data source
*/
- PlayerAuth buildPlayerAuth();
+ PlayerAuth buildPlayerAuth(P params);
/**
* Follow-up method called after the player auth could be added into the database.
+ *
+ * @param params the parameters for the registration
*/
- void executePostPersistAction();
+ void executePostPersistAction(P params);
}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java
deleted file mode 100644
index 286a51634..000000000
--- a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationExecutorProvider.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package fr.xephi.authme.process.register.executors;
-
-import org.bukkit.entity.Player;
-
-import javax.inject.Inject;
-
-/**
- * Provides a {@link RegistrationExecutor} for various registration methods.
- */
-public class RegistrationExecutorProvider {
-
- @Inject
- private PasswordRegisterExecutorProvider passwordRegisterExecutorProvider;
-
- @Inject
- private EmailRegisterExecutorProvider emailRegisterExecutorProvider;
-
- RegistrationExecutorProvider() {
- }
-
- public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, String email) {
- return passwordRegisterExecutorProvider.new PasswordRegisterExecutor(player, password, email);
- }
-
- public RegistrationExecutor getPasswordRegisterExecutor(Player player, String password, boolean loginAfterRegister) {
- return passwordRegisterExecutorProvider.new ApiPasswordRegisterExecutor(player, password, loginAfterRegister);
- }
-
- public RegistrationExecutor getTwoFactorRegisterExecutor(Player player) {
- return passwordRegisterExecutorProvider.new TwoFactorRegisterExecutor(player);
- }
-
- public RegistrationExecutor getEmailRegisterExecutor(Player player, String email) {
- return emailRegisterExecutorProvider.new EmailRegisterExecutor(player, email);
- }
-}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java
new file mode 100644
index 000000000..f5f38b866
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationMethod.java
@@ -0,0 +1,53 @@
+package fr.xephi.authme.process.register.executors;
+
+/**
+ * Methods with which a player can be registered.
+ *
+ * These constants each define a different way of registering a player and define the
+ * {@link RegistrationParameters parameters} and {@link RegistrationExecutor executor}
+ * classes which perform this registration method. This is essentially a typed enum
+ * as passing a constant of this class along with a parameters object to a method can
+ * be restricted to the correct parameters type.
+ *
+ * @param
the registration parameters type the method uses
+ */
+public final class RegistrationMethod
{
+
+ /**
+ * Password registration.
+ */
+ public static final RegistrationMethod PASSWORD_REGISTRATION =
+ new RegistrationMethod<>(PasswordRegisterExecutor.class);
+
+ /**
+ * Registration with two-factor authentication as login means.
+ */
+ public static final RegistrationMethod TWO_FACTOR_REGISTRATION =
+ new RegistrationMethod<>(TwoFactorRegisterExecutor.class);
+
+ /**
+ * Email registration: an email address is provided, to which a generated password is sent.
+ */
+ public static final RegistrationMethod EMAIL_REGISTRATION =
+ new RegistrationMethod<>(EmailRegisterExecutor.class);
+
+ /**
+ * API registration: player and password are provided via an API method.
+ */
+ public static final RegistrationMethod API_REGISTRATION =
+ new RegistrationMethod<>(ApiPasswordRegisterExecutor.class);
+
+
+ private final Class extends RegistrationExecutor> executorClass;
+
+ private RegistrationMethod(Class extends RegistrationExecutor
> executorClass) {
+ this.executorClass = executorClass;
+ }
+
+ /**
+ * @return the executor class to perform the registration method
+ */
+ public Class extends RegistrationExecutor
> getExecutorClass() {
+ return executorClass;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java
new file mode 100644
index 000000000..c92d57ffa
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/RegistrationParameters.java
@@ -0,0 +1,28 @@
+package fr.xephi.authme.process.register.executors;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Parent of all registration parameters.
+ */
+public abstract class RegistrationParameters {
+
+ private final Player player;
+
+ /**
+ * Constructor.
+ *
+ * @param player the player to perform the registration for
+ */
+ public RegistrationParameters(Player player) {
+ this.player = player;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public String getPlayerName() {
+ return player.getName();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java
new file mode 100644
index 000000000..027a5fa68
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterExecutor.java
@@ -0,0 +1,43 @@
+package fr.xephi.authme.process.register.executors;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.security.crypts.TwoFactor;
+import fr.xephi.authme.service.CommonService;
+import org.bukkit.Bukkit;
+
+import javax.inject.Inject;
+
+import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
+
+/**
+ * Executor for two-factor registration.
+ */
+class TwoFactorRegisterExecutor extends AbstractPasswordRegisterExecutor {
+
+ @Inject
+ private CommonService commonService;
+
+ @Override
+ public boolean isRegistrationAdmitted(TwoFactorRegisterParams params) {
+ // nothing to check
+ return true;
+ }
+
+ @Override
+ protected PlayerAuth createPlayerAuthObject(TwoFactorRegisterParams params) {
+ return createPlayerAuth(params.getPlayer(), params.getHashedPassword(), null);
+ }
+
+ @Override
+ public void executePostPersistAction(TwoFactorRegisterParams params) {
+ super.executePostPersistAction(params);
+
+ // Note ljacqu 20170317: This two-factor registration type is only invoked when the password hash is configured
+ // to two-factor authentication. Therefore, the hashed password is the result of the TwoFactor EncryptionMethod
+ // implementation (contains the TOTP secret).
+ String hash = params.getHashedPassword().getHash();
+ String qrCodeUrl = TwoFactor.getQRBarcodeURL(params.getPlayerName(), Bukkit.getIp(), hash);
+ commonService.send(params.getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java
new file mode 100644
index 000000000..a7a758750
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/process/register/executors/TwoFactorRegisterParams.java
@@ -0,0 +1,23 @@
+package fr.xephi.authme.process.register.executors;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Parameters for registration with two-factor authentication.
+ */
+public class TwoFactorRegisterParams extends AbstractPasswordRegisterParams {
+
+ protected TwoFactorRegisterParams(Player player) {
+ super(player);
+ }
+
+ /**
+ * Creates a parameters object.
+ *
+ * @param player the player to register
+ * @return params object with the given player
+ */
+ public static TwoFactorRegisterParams of(Player player) {
+ return new TwoFactorRegisterParams(player);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java
index 764e56768..eabd902eb 100644
--- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java
+++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java
@@ -3,19 +3,18 @@ package fr.xephi.authme.process.unregister;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
-import fr.xephi.authme.data.limbo.LimboCache;
+import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupHandler;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.process.AsynchronousProcess;
-import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.CommonService;
+import fr.xephi.authme.service.TeleportationService;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
-import fr.xephi.authme.task.LimboPlayerTaskManager;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.service.TeleportationService;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
@@ -43,10 +42,7 @@ public class AsynchronousUnregister implements AsynchronousProcess {
private BukkitService bukkitService;
@Inject
- private LimboCache limboCache;
-
- @Inject
- private LimboPlayerTaskManager limboPlayerTaskManager;
+ private LimboService limboService;
@Inject
private TeleportationService teleportationService;
@@ -111,12 +107,10 @@ public class AsynchronousUnregister implements AsynchronousProcess {
teleportationService.teleportOnJoin(player);
player.saveData();
- limboCache.deletePlayerData(player);
- limboCache.addPlayerData(player);
-
- limboPlayerTaskManager.registerTimeoutTask(player);
- limboPlayerTaskManager.registerMessageTask(name, false);
- applyBlindEffect(player);
+ bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() -> {
+ limboService.createLimboPlayer(player, false);
+ applyBlindEffect(player);
+ });
}
authGroupHandler.setGroup(player, AuthGroupType.UNREGISTERED);
service.send(player, MessageKey.UNREGISTERED_SUCCESS);
@@ -124,13 +118,8 @@ public class AsynchronousUnregister implements AsynchronousProcess {
private void applyBlindEffect(final Player player) {
if (service.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
- final int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
- bukkitService.runTask(new Runnable() {
- @Override
- public void run() {
- player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2));
- }
- });
+ int timeout = service.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
+ player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeout, 2));
}
}
}
diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java
index 732582e03..f12da678d 100644
--- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java
+++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java
@@ -7,36 +7,36 @@ import fr.xephi.authme.security.crypts.EncryptionMethod;
*/
public enum HashAlgorithm {
- BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class),
- BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class),
- CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class),
- DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class),
- IPB3(fr.xephi.authme.security.crypts.IPB3.class),
- IPB4(fr.xephi.authme.security.crypts.IPB4.class),
- JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class),
- MD5(fr.xephi.authme.security.crypts.MD5.class),
- MD5VB(fr.xephi.authme.security.crypts.MD5VB.class),
- MYBB(fr.xephi.authme.security.crypts.MYBB.class),
+ BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class),
+ BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class),
+ CRAZYCRYPT1(fr.xephi.authme.security.crypts.CrazyCrypt1.class),
+ DOUBLEMD5(fr.xephi.authme.security.crypts.DoubleMd5.class),
+ IPB3(fr.xephi.authme.security.crypts.Ipb3.class),
+ IPB4(fr.xephi.authme.security.crypts.Ipb4.class),
+ JOOMLA(fr.xephi.authme.security.crypts.Joomla.class),
+ MD5(fr.xephi.authme.security.crypts.Md5.class),
+ MD5VB(fr.xephi.authme.security.crypts.Md5vB.class),
+ MYBB(fr.xephi.authme.security.crypts.MyBB.class),
PBKDF2(fr.xephi.authme.security.crypts.Pbkdf2.class),
PBKDF2DJANGO(fr.xephi.authme.security.crypts.Pbkdf2Django.class),
- PHPBB(fr.xephi.authme.security.crypts.PHPBB.class),
- PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class),
+ PHPBB(fr.xephi.authme.security.crypts.PhpBB.class),
+ PHPFUSION(fr.xephi.authme.security.crypts.PhpFusion.class),
@Deprecated
- PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class),
- ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class),
- SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class),
- SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class),
- SHA1(fr.xephi.authme.security.crypts.SHA1.class),
- SHA256(fr.xephi.authme.security.crypts.SHA256.class),
- SHA512(fr.xephi.authme.security.crypts.SHA512.class),
- SMF(fr.xephi.authme.security.crypts.SMF.class),
+ PLAINTEXT(fr.xephi.authme.security.crypts.PlainText.class),
+ ROYALAUTH(fr.xephi.authme.security.crypts.RoyalAuth.class),
+ SALTED2MD5(fr.xephi.authme.security.crypts.Salted2Md5.class),
+ SALTEDSHA512(fr.xephi.authme.security.crypts.SaltedSha512.class),
+ SHA1(fr.xephi.authme.security.crypts.Sha1.class),
+ SHA256(fr.xephi.authme.security.crypts.Sha256.class),
+ SHA512(fr.xephi.authme.security.crypts.Sha512.class),
+ SMF(fr.xephi.authme.security.crypts.Smf.class),
TWO_FACTOR(fr.xephi.authme.security.crypts.TwoFactor.class),
- WBB3(fr.xephi.authme.security.crypts.WBB3.class),
- WBB4(fr.xephi.authme.security.crypts.WBB4.class),
- WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class),
- WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class),
- XAUTH(fr.xephi.authme.security.crypts.XAUTH.class),
- XFBCRYPT(fr.xephi.authme.security.crypts.XFBCRYPT.class),
+ WBB3(fr.xephi.authme.security.crypts.Wbb3.class),
+ WBB4(fr.xephi.authme.security.crypts.Wbb4.class),
+ WHIRLPOOL(fr.xephi.authme.security.crypts.Whirlpool.class),
+ WORDPRESS(fr.xephi.authme.security.crypts.Wordpress.class),
+ XAUTH(fr.xephi.authme.security.crypts.XAuth.class),
+ XFBCRYPT(fr.xephi.authme.security.crypts.XfBCrypt.class),
CUSTOM(null);
private final Class extends EncryptionMethod> clazz;
diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
index 8549b388b..d29471089 100644
--- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
+++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
@@ -1,9 +1,9 @@
package fr.xephi.authme.security;
-import ch.jalu.injector.Injector;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
@@ -29,7 +29,7 @@ public class PasswordSecurity implements Reloadable {
private PluginManager pluginManager;
@Inject
- private Injector injector;
+ private Factory hashAlgorithmFactory;
private HashAlgorithm algorithm;
private Collection legacyAlgorithms;
@@ -154,7 +154,7 @@ public class PasswordSecurity implements Reloadable {
if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) {
return null;
}
- return injector.newInstance(algorithm.getClazz());
+ return hashAlgorithmFactory.newInstance(algorithm.getClazz());
}
private void hashPasswordForNewAlgorithm(String password, String playerName) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
similarity index 95%
rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java
rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
index aae6b9109..02e12d459 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt.java
@@ -14,12 +14,12 @@ import javax.inject.Inject;
@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8
@HasSalt(value = SaltType.TEXT) // length depends on the bcryptLog2Rounds setting
-public class BCRYPT implements EncryptionMethod {
+public class BCrypt implements EncryptionMethod {
private final int bCryptLog2Rounds;
@Inject
- public BCRYPT(Settings settings) {
+ public BCrypt(Settings settings) {
bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND);
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
similarity index 95%
rename from src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java
rename to src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
index 49bd45f8a..cf4807abc 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/BCrypt2y.java
@@ -4,7 +4,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.RECOMMENDED)
-public class BCRYPT2Y extends HexSaltedMethod {
+public class BCrypt2y extends HexSaltedMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java
similarity index 95%
rename from src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java
rename to src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java
index 595842314..6130c6a12 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/CrazyCrypt1.java
@@ -6,7 +6,7 @@ import fr.xephi.authme.security.MessageDigestAlgorithm;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
-public class CRAZYCRYPT1 extends UsernameSaltMethod {
+public class CrazyCrypt1 extends UsernameSaltMethod {
private static final char[] CRYPTCHARS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java
deleted file mode 100644
index b7823a2b4..000000000
--- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package fr.xephi.authme.security.crypts;
-
-import static fr.xephi.authme.security.HashUtils.md5;
-
-public class DOUBLEMD5 extends UnsaltedMethod {
-
- @Override
- public String computeHash(String password) {
- return md5(md5(password));
- }
-
-}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java
new file mode 100644
index 000000000..54158419b
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/DoubleMd5.java
@@ -0,0 +1,16 @@
+package fr.xephi.authme.security.crypts;
+
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
+import static fr.xephi.authme.security.HashUtils.md5;
+
+@Recommendation(Usage.DEPRECATED)
+public class DoubleMd5 extends UnsaltedMethod {
+
+ @Override
+ public String computeHash(String password) {
+ return md5(md5(password));
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java
similarity index 93%
rename from src/main/java/fr/xephi/authme/security/crypts/IPB3.java
rename to src/main/java/fr/xephi/authme/security/crypts/Ipb3.java
index a4e62461c..15dcd189b 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb3.java
@@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 5)
-public class IPB3 extends SeparateSaltMethod {
+public class Ipb3 extends SeparateSaltMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
similarity index 91%
rename from src/main/java/fr/xephi/authme/security/crypts/IPB4.java
rename to src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
index 40ea75166..762897955 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/IPB4.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Ipb4.java
@@ -11,15 +11,15 @@ import fr.xephi.authme.util.StringUtils;
/**
- * Implementation for IPB4 (Invision Power Board 4).
+ * Implementation for Ipb4 (Invision Power Board 4).
*
* The hash uses standard BCrypt with 13 as log2 number of rounds. Additionally,
- * IPB4 requires that the salt be stored additionally in the column "members_pass_hash"
+ * Ipb4 requires that the salt be stored additionally in the column "members_pass_hash"
* (even though BCrypt hashes already have the salt in the result).
*/
@Recommendation(Usage.DOES_NOT_WORK)
@HasSalt(value = SaltType.TEXT, length = 22)
-public class IPB4 implements EncryptionMethod {
+public class Ipb4 implements EncryptionMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java
similarity index 94%
rename from src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java
rename to src/main/java/fr/xephi/authme/security/crypts/Joomla.java
index ca72674b3..462f5cb28 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Joomla.java
@@ -5,7 +5,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.ACCEPTABLE)
-public class JOOMLA extends HexSaltedMethod {
+public class Joomla extends HexSaltedMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java
deleted file mode 100644
index 50bb7d97e..000000000
--- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package fr.xephi.authme.security.crypts;
-
-import fr.xephi.authme.security.HashUtils;
-
-public class MD5 extends UnsaltedMethod {
-
- @Override
- public String computeHash(String password) {
- return HashUtils.md5(password);
- }
-
-}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Md5.java b/src/main/java/fr/xephi/authme/security/crypts/Md5.java
new file mode 100644
index 000000000..a5f6d91d2
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/Md5.java
@@ -0,0 +1,15 @@
+package fr.xephi.authme.security.crypts;
+
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
+@Recommendation(Usage.DEPRECATED)
+public class Md5 extends UnsaltedMethod {
+
+ @Override
+ public String computeHash(String password) {
+ return HashUtils.md5(password);
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
similarity index 93%
rename from src/main/java/fr/xephi/authme/security/crypts/MD5VB.java
rename to src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
index f9c21ae7e..c244ec49d 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Md5vB.java
@@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts;
import static fr.xephi.authme.security.HashUtils.md5;
-public class MD5VB extends HexSaltedMethod {
+public class Md5vB extends HexSaltedMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java
similarity index 93%
rename from src/main/java/fr/xephi/authme/security/crypts/MYBB.java
rename to src/main/java/fr/xephi/authme/security/crypts/MyBB.java
index b25f47695..3f0a477ab 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/MyBB.java
@@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 8)
-public class MYBB extends SeparateSaltMethod {
+public class MyBB extends SeparateSaltMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
similarity index 99%
rename from src/main/java/fr/xephi/authme/security/crypts/PHPBB.java
rename to src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
index 074143fd6..e5f7e54ca 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PhpBB.java
@@ -9,7 +9,7 @@ import java.security.MessageDigest;
/**
* @author stefano
*/
-public class PHPBB extends HexSaltedMethod {
+public class PhpBB extends HexSaltedMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java
similarity index 96%
rename from src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java
rename to src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java
index 905798ec3..5a49ed4ce 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PhpFusion.java
@@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
@Recommendation(Usage.DO_NOT_USE)
@AsciiRestricted
-public class PHPFUSION extends SeparateSaltMethod {
+public class PhpFusion extends SeparateSaltMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java
similarity index 57%
rename from src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java
rename to src/main/java/fr/xephi/authme/security/crypts/PlainText.java
index a294ca917..43d6e8200 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/PlainText.java
@@ -1,12 +1,16 @@
package fr.xephi.authme.security.crypts;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
/**
* Plaintext password storage.
*
* @deprecated Using this is no longer supported. AuthMe will migrate to SHA256 on startup.
*/
@Deprecated
-public class PLAINTEXT extends UnsaltedMethod {
+@Recommendation(Usage.DEPRECATED)
+public class PlainText extends UnsaltedMethod {
@Override
public String computeHash(String password) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java
similarity index 88%
rename from src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java
rename to src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java
index db089e360..989ef8383 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/RoyalAuth.java
@@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.HashUtils;
-public class ROYALAUTH extends UnsaltedMethod {
+public class RoyalAuth extends UnsaltedMethod {
@Override
public String computeHash(String password) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java
deleted file mode 100644
index 080910ec7..000000000
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package fr.xephi.authme.security.crypts;
-
-import fr.xephi.authme.security.HashUtils;
-
-public class SHA1 extends UnsaltedMethod {
-
- @Override
- public String computeHash(String password) {
- return HashUtils.sha1(password);
- }
-
-}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java
deleted file mode 100644
index 81f1e0263..000000000
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package fr.xephi.authme.security.crypts;
-
-import fr.xephi.authme.security.HashUtils;
-
-public class SHA512 extends UnsaltedMethod {
-
- @Override
- public String computeHash(String password) {
- return HashUtils.sha512(password);
- }
-
-}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java
similarity index 91%
rename from src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java
rename to src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java
index b2f22ab9b..6d708b3ce 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Salted2Md5.java
@@ -14,12 +14,12 @@ import static fr.xephi.authme.security.HashUtils.md5;
@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8)
@HasSalt(value = SaltType.TEXT) // length defined by the doubleMd5SaltLength setting
-public class SALTED2MD5 extends SeparateSaltMethod {
+public class Salted2Md5 extends SeparateSaltMethod {
private final int saltLength;
@Inject
- public SALTED2MD5(Settings settings) {
+ public Salted2Md5(Settings settings) {
saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH);
}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java
similarity index 90%
rename from src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java
rename to src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java
index f0f293439..b5660d658 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SaltedSha512.java
@@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.RECOMMENDED)
-public class SALTEDSHA512 extends SeparateSaltMethod {
+public class SaltedSha512 extends SeparateSaltMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
index 7d4b3d952..d0dacda4d 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
@@ -8,15 +8,15 @@ public abstract class SeparateSaltMethod implements EncryptionMethod {
@Override
public abstract String computeHash(String password, String salt, String name);
- @Override
- public abstract String generateSalt();
-
@Override
public HashedPassword computeHash(String password, String name) {
String salt = generateSalt();
return new HashedPassword(computeHash(password, salt, name), salt);
}
+ @Override
+ public abstract String generateSalt();
+
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null));
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha1.java b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java
new file mode 100644
index 000000000..3430752b5
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/Sha1.java
@@ -0,0 +1,15 @@
+package fr.xephi.authme.security.crypts;
+
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
+@Recommendation(Usage.DEPRECATED)
+public class Sha1 extends UnsaltedMethod {
+
+ @Override
+ public String computeHash(String password) {
+ return HashUtils.sha1(password);
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java
similarity index 94%
rename from src/main/java/fr/xephi/authme/security/crypts/SHA256.java
rename to src/main/java/fr/xephi/authme/security/crypts/Sha256.java
index ee55d4512..1b77a2e44 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Sha256.java
@@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.HashUtils.sha256;
@Recommendation(Usage.RECOMMENDED)
-public class SHA256 extends HexSaltedMethod {
+public class Sha256 extends HexSaltedMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/Sha512.java b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java
new file mode 100644
index 000000000..4e9682ffe
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/security/crypts/Sha512.java
@@ -0,0 +1,15 @@
+package fr.xephi.authme.security.crypts;
+
+import fr.xephi.authme.security.HashUtils;
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
+@Recommendation(Usage.DEPRECATED)
+public class Sha512 extends UnsaltedMethod {
+
+ @Override
+ public String computeHash(String password) {
+ return HashUtils.sha512(password);
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/Smf.java
similarity index 85%
rename from src/main/java/fr/xephi/authme/security/crypts/SMF.java
rename to src/main/java/fr/xephi/authme/security/crypts/Smf.java
index 175efc3fa..46114d2e9 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Smf.java
@@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts;
import fr.xephi.authme.security.HashUtils;
-public class SMF extends UsernameSaltMethod {
+public class Smf extends UsernameSaltMethod {
@Override
public HashedPassword computeHash(String password, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
index 698979d8a..23101e22a 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
@@ -17,13 +17,13 @@ public abstract class UsernameSaltMethod implements EncryptionMethod {
public abstract HashedPassword computeHash(String password, String name);
@Override
- public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- return hashedPassword.getHash().equals(computeHash(password, name).getHash());
+ public String computeHash(String password, String salt, String name) {
+ return computeHash(password, name).getHash();
}
@Override
- public String computeHash(String password, String salt, String name) {
- return computeHash(password, name).getHash();
+ public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
+ return hashedPassword.getHash().equals(computeHash(password, name).getHash());
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java
similarity index 94%
rename from src/main/java/fr/xephi/authme/security/crypts/WBB3.java
rename to src/main/java/fr/xephi/authme/security/crypts/Wbb3.java
index 546c2dc87..0a042b489 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb3.java
@@ -10,7 +10,7 @@ import static fr.xephi.authme.security.HashUtils.sha1;
@Recommendation(Usage.ACCEPTABLE)
@HasSalt(value = SaltType.TEXT, length = 40)
-public class WBB3 extends SeparateSaltMethod {
+public class Wbb3 extends SeparateSaltMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
similarity index 96%
rename from src/main/java/fr/xephi/authme/security/crypts/WBB4.java
rename to src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
index a81503374..d1d4953d1 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Wbb4.java
@@ -6,7 +6,7 @@ import fr.xephi.authme.security.crypts.description.Usage;
import static fr.xephi.authme.security.crypts.BCryptService.hashpw;
@Recommendation(Usage.RECOMMENDED)
-public class WBB4 extends HexSaltedMethod {
+public class Wbb4 extends HexSaltedMethod {
@Override
public String computeHash(String password, String salt, String name) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java
similarity index 98%
rename from src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java
rename to src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java
index 72d8e8fb6..c1ea747a9 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java
@@ -59,9 +59,13 @@ package fr.xephi.authme.security.crypts;
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+import fr.xephi.authme.security.crypts.description.Recommendation;
+import fr.xephi.authme.security.crypts.description.Usage;
+
import java.util.Arrays;
-public class WHIRLPOOL extends UnsaltedMethod {
+@Recommendation(Usage.DEPRECATED)
+public class Whirlpool extends UnsaltedMethod {
/**
* The message digest size (in bits)
@@ -158,7 +162,7 @@ public class WHIRLPOOL extends UnsaltedMethod {
protected final long[] block = new long[8];
protected final long[] state = new long[8];
- public WHIRLPOOL() {
+ public Whirlpool() {
}
protected static String display(byte[] array) {
diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
similarity index 98%
rename from src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java
rename to src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
index f331d1fc6..768b92c5d 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/Wordpress.java
@@ -16,7 +16,7 @@ import java.util.Arrays;
@HasSalt(value = SaltType.TEXT, length = 9)
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
// and isn't exposed to the outside, so we treat it as an unsalted implementation
-public class WORDPRESS extends UnsaltedMethod {
+public class Wordpress extends UnsaltedMethod {
private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private final SecureRandom randomGen = new SecureRandom();
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java
similarity index 87%
rename from src/main/java/fr/xephi/authme/security/crypts/XAUTH.java
rename to src/main/java/fr/xephi/authme/security/crypts/XAuth.java
index f2ebf1976..9f921b6ae 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XAuth.java
@@ -4,15 +4,15 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
@Recommendation(Usage.RECOMMENDED)
-public class XAUTH extends HexSaltedMethod {
+public class XAuth extends HexSaltedMethod {
private static String getWhirlpool(String message) {
- WHIRLPOOL w = new WHIRLPOOL();
- byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES];
+ Whirlpool w = new Whirlpool();
+ byte[] digest = new byte[Whirlpool.DIGESTBYTES];
w.NESSIEinit();
w.NESSIEadd(message);
w.NESSIEfinalize(digest);
- return WHIRLPOOL.display(digest);
+ return Whirlpool.display(digest);
}
@Override
diff --git a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
similarity index 97%
rename from src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java
rename to src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
index a20ee65aa..bf5545290 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XfBCrypt.java
@@ -7,7 +7,7 @@ import fr.xephi.authme.util.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class XFBCRYPT implements EncryptionMethod {
+public class XfBCrypt implements EncryptionMethod {
public static final String SCHEME_CLASS = "XenForo_Authentication_Core12";
private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\"");
diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java
index c11c335ac..9ccc52c4f 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java
@@ -20,6 +20,9 @@ public enum Usage {
/** Hash algorithm is not recommended to be used. Use only if required by another system. */
DO_NOT_USE,
+ /** Algorithm that is or will be no longer supported actively. */
+ DEPRECATED,
+
/** The algorithm does not work properly; do not use. */
DOES_NOT_WORK
diff --git a/src/main/java/fr/xephi/authme/service/AntiBotService.java b/src/main/java/fr/xephi/authme/service/AntiBotService.java
index 91f80041d..90c62a24b 100644
--- a/src/main/java/fr/xephi/authme/service/AntiBotService.java
+++ b/src/main/java/fr/xephi/authme/service/AntiBotService.java
@@ -30,7 +30,6 @@ public class AntiBotService implements SettingsDependent {
// Settings
private int duration;
private int sensibility;
- private int delay;
private int interval;
// Service status
private AntiBotStatus antiBotStatus;
@@ -60,7 +59,6 @@ public class AntiBotService implements SettingsDependent {
// Load settings
duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION);
sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY);
- delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY);
interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL);
// Stop existing protection
@@ -77,6 +75,7 @@ public class AntiBotService implements SettingsDependent {
// Delay the schedule on first start
if (startup) {
+ int delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY);
bukkitService.scheduleSyncDelayedTask(enableTask, delay * TICKS_PER_SECOND);
startup = false;
} else {
diff --git a/src/main/java/fr/xephi/authme/service/BukkitService.java b/src/main/java/fr/xephi/authme/service/BukkitService.java
index 14cf0dcb7..4d94b62d5 100644
--- a/src/main/java/fr/xephi/authme/service/BukkitService.java
+++ b/src/main/java/fr/xephi/authme/service/BukkitService.java
@@ -13,7 +13,6 @@ import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
-import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
@@ -45,7 +44,7 @@ public class BukkitService implements SettingsDependent {
@Inject
BukkitService(AuthMe authMe, Settings settings) {
this.authMe = authMe;
- getOnlinePlayersIsCollection = initializeOnlinePlayersIsCollectionField();
+ getOnlinePlayersIsCollection = doesOnlinePlayersMethodReturnCollection();
reload(settings);
}
@@ -172,7 +171,7 @@ public class BukkitService implements SettingsDependent {
* @return a BukkitTask that contains the id number
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
- * @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long)
+ * @see BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)
*/
public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) {
return task.runTaskTimer(authMe, delay, period);
@@ -302,11 +301,12 @@ public class BukkitService implements SettingsDependent {
/**
* Method run upon initialization to verify whether or not the Bukkit implementation
- * returns the online players as a Collection.
+ * returns the online players as a {@link Collection}.
*
+ * @return true if a collection is returned by the bukkit implementation, false otherwise
* @see #getOnlinePlayers()
*/
- private static boolean initializeOnlinePlayersIsCollectionField() {
+ private static boolean doesOnlinePlayersMethodReturnCollection() {
try {
Method method = Bukkit.class.getDeclaredMethod("getOnlinePlayers");
return method.getReturnType() == Collection.class;
diff --git a/src/main/java/fr/xephi/authme/service/CommonService.java b/src/main/java/fr/xephi/authme/service/CommonService.java
index c181f83b0..d2381a455 100644
--- a/src/main/java/fr/xephi/authme/service/CommonService.java
+++ b/src/main/java/fr/xephi/authme/service/CommonService.java
@@ -65,16 +65,6 @@ public class CommonService {
messages.send(sender, key, replacements);
}
- /**
- * Retrieves a message.
- *
- * @param key the key of the message
- * @return the message, split by line
- */
- public String[] retrieveMessage(MessageKey key) {
- return messages.retrieve(key);
- }
-
/**
* Retrieves a message in one piece.
*
@@ -101,10 +91,10 @@ public class CommonService {
*
* @param player the player to process
* @param group the group to add the player to
- * @return true on success, false otherwise
*/
- public boolean setGroup(Player player, AuthGroupType group) {
- return authGroupHandler.setGroup(player, group);
+ // TODO ljacqu 20170304: Move this out of CommonService
+ public void setGroup(Player player, AuthGroupType group) {
+ authGroupHandler.setGroup(player, group);
}
}
diff --git a/src/main/java/fr/xephi/authme/service/JoinMessageService.java b/src/main/java/fr/xephi/authme/service/JoinMessageService.java
new file mode 100644
index 000000000..2fa5766c7
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/service/JoinMessageService.java
@@ -0,0 +1,46 @@
+package fr.xephi.authme.service;
+
+import fr.xephi.authme.util.StringUtils;
+
+import javax.inject.Inject;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * The JoinMessageService class.
+ */
+public class JoinMessageService {
+
+ private BukkitService bukkitService;
+
+ private Map joinMessages;
+
+ @Inject
+ JoinMessageService(BukkitService bukkitService) {
+ this.bukkitService = bukkitService;
+ joinMessages = new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Store a join message.
+ *
+ * @param playerName the player name
+ * @param string the join message
+ */
+ public void putMessage(String playerName, String string) {
+ joinMessages.put(playerName, string);
+ }
+
+ /**
+ * Broadcast the join message of the specified player.
+ *
+ * @param playerName the player name
+ */
+ public void sendMessage(String playerName) {
+ String joinMessage = joinMessages.remove(playerName);
+ if (StringUtils.isEmpty(joinMessage)) {
+ return;
+ }
+ bukkitService.broadcastMessage(joinMessage);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/service/MigrationService.java b/src/main/java/fr/xephi/authme/service/MigrationService.java
index 043de8988..338ef51a1 100644
--- a/src/main/java/fr/xephi/authme/service/MigrationService.java
+++ b/src/main/java/fr/xephi/authme/service/MigrationService.java
@@ -5,7 +5,7 @@ import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.HashedPassword;
-import fr.xephi.authme.security.crypts.SHA256;
+import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
@@ -20,14 +20,14 @@ public final class MigrationService {
}
/**
- * Hash all passwords to SHA256 and updated the setting if the password hash is set to the deprecated PLAINTEXT.
+ * Hash all passwords to Sha256 and updated the setting if the password hash is set to the deprecated PLAINTEXT.
*
* @param settings The settings instance
* @param dataSource The data source
- * @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation
+ * @param authmeSha256 Instance to the AuthMe Sha256 encryption method implementation
*/
public static void changePlainTextToSha256(Settings settings, DataSource dataSource,
- SHA256 authmeSha256) {
+ Sha256 authmeSha256) {
if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) {
ConsoleLogger.warning("Your HashAlgorithm has been detected as plaintext and is now deprecated;"
+ " it will be changed and hashed now to the AuthMe default hashing method");
diff --git a/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java
new file mode 100644
index 000000000..f24b45f35
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/service/PasswordRecoveryService.java
@@ -0,0 +1,173 @@
+package fr.xephi.authme.service;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.datasource.DataSource;
+import fr.xephi.authme.initialization.HasCleanup;
+import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.mail.EmailService;
+import fr.xephi.authme.message.MessageKey;
+import fr.xephi.authme.message.Messages;
+import fr.xephi.authme.security.PasswordSecurity;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.settings.properties.SecuritySettings;
+import fr.xephi.authme.util.PlayerUtils;
+import fr.xephi.authme.util.RandomStringUtils;
+import fr.xephi.authme.util.expiring.Duration;
+import fr.xephi.authme.util.expiring.ExpiringMap;
+import fr.xephi.authme.util.expiring.ExpiringSet;
+import org.bukkit.entity.Player;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.concurrent.TimeUnit;
+
+import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
+
+/**
+ * Manager for password recovery.
+ */
+public class PasswordRecoveryService implements Reloadable, HasCleanup {
+
+ @Inject
+ private CommonService commonService;
+
+ @Inject
+ private DataSource dataSource;
+
+ @Inject
+ private EmailService emailService;
+
+ @Inject
+ private PasswordSecurity passwordSecurity;
+
+ @Inject
+ private RecoveryCodeService recoveryCodeService;
+
+ @Inject
+ private Messages messages;
+
+ private ExpiringSet emailCooldown;
+ private ExpiringMap successfulRecovers;
+
+ @PostConstruct
+ private void initEmailCooldownSet() {
+ emailCooldown = new ExpiringSet<>(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ successfulRecovers = new ExpiringMap<>(
+ commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES);
+ }
+
+ /**
+ * Create a new recovery code and send it to the player
+ * via email.
+ *
+ * @param player The player getting the code.
+ * @param email The email to send the code to.
+ */
+ public void createAndSendRecoveryCode(Player player, String email) {
+ if (!checkEmailCooldown(player)) {
+ return;
+ }
+
+ String recoveryCode = recoveryCodeService.generateCode(player.getName());
+ boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode);
+ if (couldSendMail) {
+ commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
+ emailCooldown.add(player.getName().toLowerCase());
+ } else {
+ commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
+ }
+ }
+
+ /**
+ * Generate a new password and send it to the player via
+ * email. This will update the database with the new password.
+ *
+ * @param player The player recovering their password.
+ * @param email The email to send the password to.
+ */
+ public void generateAndSendNewPassword(Player player, String email) {
+ if (!checkEmailCooldown(player)) {
+ return;
+ }
+
+ String name = player.getName();
+ String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
+ HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
+
+ ConsoleLogger.info("Generating new password for '" + name + "'");
+
+ dataSource.updatePassword(name, hashNew);
+ boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass);
+ if (couldSendMail) {
+ commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
+ emailCooldown.add(player.getName().toLowerCase());
+ } else {
+ commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
+ }
+ }
+
+ /**
+ * Allows a player to change their password after
+ * correctly entering a recovery code.
+ *
+ * @param player The player recovering their password.
+ */
+ public void addSuccessfulRecovery(Player player) {
+ String name = player.getName();
+ String address = PlayerUtils.getPlayerIp(player);
+
+ successfulRecovers.put(name, address);
+ commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD);
+ }
+
+ /**
+ * Check if a player is able to have emails sent.
+ *
+ * @param player The player to check.
+ * @return True if the player is not on cooldown.
+ */
+ private boolean checkEmailCooldown(Player player) {
+ Duration waitDuration = emailCooldown.getExpiration(player.getName().toLowerCase());
+ if (waitDuration.getDuration() > 0) {
+ String durationText = messages.formatDuration(waitDuration);
+ messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR, durationText);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks if a player can change their password after recovery
+ * using the /email setpassword command.
+ *
+ * @param player The player to check.
+ * @return True if the player can change their password.
+ */
+ public boolean canChangePassword(Player player) {
+ String name = player.getName();
+ String playerAddress = PlayerUtils.getPlayerIp(player);
+ String storedAddress = successfulRecovers.get(name);
+
+ if (storedAddress == null || !playerAddress.equals(storedAddress)) {
+ messages.send(player, MessageKey.CHANGE_PASSWORD_EXPIRED);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void reload() {
+ emailCooldown.setExpiration(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ successfulRecovers.setExpiration(
+ commonService.getProperty(SecuritySettings.PASSWORD_CHANGE_TIMEOUT), TimeUnit.MINUTES);
+ }
+
+ @Override
+ public void performCleanup() {
+ emailCooldown.removeExpiredEntries();
+ successfulRecovers.removeExpiredEntries();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
index e7fa37ad0..dcccb36de 100644
--- a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
+++ b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
@@ -1,38 +1,41 @@
package fr.xephi.authme.service;
-import com.google.common.annotations.VisibleForTesting;
+import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
-import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
+import fr.xephi.authme.util.RandomStringUtils;
+import fr.xephi.authme.util.expiring.ExpiringMap;
+import fr.xephi.authme.util.expiring.TimedCounter;
import javax.inject.Inject;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
-import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
+import java.util.concurrent.TimeUnit;
/**
* Manager for recovery codes.
*/
-public class RecoveryCodeService implements SettingsDependent {
-
- private Map recoveryCodes = new ConcurrentHashMap<>();
+public class RecoveryCodeService implements SettingsDependent, HasCleanup {
+ private final ExpiringMap recoveryCodes;
+ private final TimedCounter playerTries;
private int recoveryCodeLength;
- private long recoveryCodeExpirationMillis;
+ private int recoveryCodeExpiration;
+ private int recoveryCodeMaxTries;
@Inject
RecoveryCodeService(Settings settings) {
- reload(settings);
+ recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
+ recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID);
+ recoveryCodeMaxTries = settings.getProperty(SecuritySettings.RECOVERY_CODE_MAX_TRIES);
+ recoveryCodes = new ExpiringMap<>(recoveryCodeExpiration, TimeUnit.HOURS);
+ playerTries = new TimedCounter<>(recoveryCodeExpiration, TimeUnit.HOURS);
}
/**
* @return whether recovery codes are enabled or not
*/
public boolean isRecoveryCodeNeeded() {
- return recoveryCodeLength > 0 && recoveryCodeExpirationMillis > 0;
+ return recoveryCodeLength > 0 && recoveryCodeExpiration > 0;
}
/**
@@ -43,7 +46,9 @@ public class RecoveryCodeService implements SettingsDependent {
*/
public String generateCode(String player) {
String code = RandomStringUtils.generateHex(recoveryCodeLength);
- recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis));
+
+ playerTries.put(player, recoveryCodeMaxTries);
+ recoveryCodes.put(player, code);
return code;
}
@@ -55,11 +60,29 @@ public class RecoveryCodeService implements SettingsDependent {
* @return true if the code matches and has not expired, false otherwise
*/
public boolean isCodeValid(String player, String code) {
- ExpiringEntry entry = recoveryCodes.get(player);
- if (entry != null) {
- return code != null && code.equals(entry.getCode());
- }
- return false;
+ String storedCode = recoveryCodes.get(player);
+ playerTries.decrement(player);
+ return storedCode != null && storedCode.equals(code);
+ }
+
+ /**
+ * Checks whether a player has tries remaining to enter a code.
+ *
+ * @param player The player to check for.
+ * @return True if the player has tries left.
+ */
+ public boolean hasTriesLeft(String player) {
+ return playerTries.get(player) > 0;
+ }
+
+ /**
+ * Get the number of attempts a player has to enter a code.
+ *
+ * @param player The player to check for.
+ * @return The number of tries left.
+ */
+ public int getTriesLeft(String player) {
+ return playerTries.get(player);
}
/**
@@ -69,31 +92,20 @@ public class RecoveryCodeService implements SettingsDependent {
*/
public void removeCode(String player) {
recoveryCodes.remove(player);
+ playerTries.remove(player);
}
@Override
public void reload(Settings settings) {
recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
- recoveryCodeExpirationMillis = settings.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR;
+ recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID);
+ recoveryCodeMaxTries = settings.getProperty(SecuritySettings.RECOVERY_CODE_MAX_TRIES);
+ recoveryCodes.setExpiration(recoveryCodeExpiration, TimeUnit.HOURS);
}
- /**
- * Entry with an expiration.
- */
- @VisibleForTesting
- static final class ExpiringEntry {
-
- private final String code;
- private final long expiration;
-
- ExpiringEntry(String code, long expiration) {
- this.code = code;
- this.expiration = expiration;
- }
-
- String getCode() {
- return System.currentTimeMillis() < expiration ? code : null;
- }
+ @Override
+ public void performCleanup() {
+ recoveryCodes.removeExpiredEntries();
+ playerTries.removeExpiredEntries();
}
-
}
diff --git a/src/main/java/fr/xephi/authme/service/TeleportationService.java b/src/main/java/fr/xephi/authme/service/TeleportationService.java
index 9be5012f4..65fd84044 100644
--- a/src/main/java/fr/xephi/authme/service/TeleportationService.java
+++ b/src/main/java/fr/xephi/authme/service/TeleportationService.java
@@ -99,19 +99,19 @@ public class TeleportationService implements Reloadable {
*
* @param player the player
* @param auth corresponding PlayerAuth object
- * @param limbo corresponding PlayerData object
+ * @param limbo corresponding LimboPlayer object
*/
public void teleportOnLogin(final Player player, PlayerAuth auth, LimboPlayer limbo) {
if (settings.getProperty(RestrictionSettings.NO_TELEPORT)) {
return;
}
- // #856: If PlayerData comes from a persisted file, the Location might be null
+ // #856: If LimboPlayer comes from a persisted file, the Location might be null
String worldName = (limbo != null && limbo.getLocation() != null)
? limbo.getLocation().getWorld().getName()
: null;
- // The world in PlayerData is from where the player comes, before any teleportation by AuthMe
+ // The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe
if (mustForceSpawnAfterLogin(worldName)) {
teleportToSpawn(player, true);
} else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
@@ -148,7 +148,7 @@ public class TeleportationService implements Reloadable {
/**
* Emits the teleportation event and performs teleportation according to it (potentially modified
- * by external listeners). Note that not teleportation is performed if the event's location is empty.
+ * by external listeners). Note that no teleportation is performed if the event's location is empty.
*
* @param player the player to teleport
* @param event the event to emit and according to which to teleport
diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java
index 7905b3671..9511c6e33 100644
--- a/src/main/java/fr/xephi/authme/service/ValidationService.java
+++ b/src/main/java/fr/xephi/authme/service/ValidationService.java
@@ -1,6 +1,9 @@
package fr.xephi.authme.service;
import ch.jalu.configme.properties.Property;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.message.MessageKey;
@@ -11,9 +14,10 @@ import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.ProtectionSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
-import fr.xephi.authme.util.CollectionUtils;
+import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@@ -23,6 +27,8 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
+import static fr.xephi.authme.util.StringUtils.isInsideString;
+
/**
* Validation service.
*/
@@ -39,6 +45,7 @@ public class ValidationService implements Reloadable {
private Pattern passwordRegex;
private Set unrestrictedNames;
+ private Multimap restrictedNames;
ValidationService() {
}
@@ -49,6 +56,9 @@ public class ValidationService implements Reloadable {
passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX));
// Use Set for more efficient contains() lookup
unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES));
+ restrictedNames = settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)
+ ? loadNameRestrictions(settings.getProperty(RestrictionSettings.RESTRICTED_USERS))
+ : HashMultimap.create();
}
/**
@@ -116,9 +126,10 @@ public class ValidationService implements Reloadable {
}
String countryCode = geoIpService.getCountryCode(hostAddress);
- return validateWhitelistAndBlacklist(countryCode,
- ProtectionSettings.COUNTRIES_WHITELIST,
- ProtectionSettings.COUNTRIES_BLACKLIST);
+ boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode,
+ ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_BLACKLIST);
+ ConsoleLogger.debug("Country code `{0}` for `{1}` is allowed: {2}", countryCode, hostAddress, isCountryAllowed);
+ return isCountryAllowed;
}
/**
@@ -131,6 +142,24 @@ public class ValidationService implements Reloadable {
return unrestrictedNames.contains(name.toLowerCase());
}
+ /**
+ * Checks that the player meets any name restriction if present (IP/domain-based).
+ *
+ * @param player the player to check
+ * @return true if the player may join, false if the player does not satisfy the name restrictions
+ */
+ public boolean fulfillsNameRestrictions(Player player) {
+ Collection restrictions = restrictedNames.get(player.getName().toLowerCase());
+ if (Utils.isCollectionEmpty(restrictions)) {
+ return true;
+ }
+
+ String ip = PlayerUtils.getPlayerIp(player);
+ String domain = player.getAddress().getHostName();
+ return restrictions.stream()
+ .anyMatch(restriction -> ip.equals(restriction) || domain.equalsIgnoreCase(restriction));
+ }
+
/**
* Verifies whether the given value is allowed according to the given whitelist and blacklist settings.
* Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present
@@ -144,11 +173,11 @@ public class ValidationService implements Reloadable {
private boolean validateWhitelistAndBlacklist(String value, Property> whitelist,
Property> blacklist) {
List whitelistValue = settings.getProperty(whitelist);
- if (!CollectionUtils.isEmpty(whitelistValue)) {
+ if (!Utils.isCollectionEmpty(whitelistValue)) {
return containsIgnoreCase(whitelistValue, value);
}
List blacklistValue = settings.getProperty(blacklist);
- return CollectionUtils.isEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value);
+ return Utils.isCollectionEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value);
}
private static boolean containsIgnoreCase(Collection coll, String needle) {
@@ -160,6 +189,26 @@ public class ValidationService implements Reloadable {
return false;
}
+ /**
+ * Loads the configured name restrictions into a Multimap by player name (all-lowercase).
+ *
+ * @param configuredRestrictions the restriction rules to convert to a map
+ * @return map of allowed IPs/domain names by player name
+ */
+ private Multimap loadNameRestrictions(List configuredRestrictions) {
+ Multimap restrictions = HashMultimap.create();
+ for (String restriction : configuredRestrictions) {
+ if (isInsideString(';', restriction)) {
+ String[] data = restriction.split(";");
+ restrictions.put(data[0].toLowerCase(), data[1]);
+ } else {
+ ConsoleLogger.warning("Restricted user rule must have a ';' separating name from restriction,"
+ + " but found: '" + restriction + "'");
+ }
+ }
+ return restrictions;
+ }
+
public static final class ValidationResult {
private final MessageKey messageKey;
private final String[] args;
@@ -195,6 +244,7 @@ public class ValidationService implements Reloadable {
public MessageKey getMessageKey() {
return messageKey;
}
+
public String[] getArgs() {
return args;
}
diff --git a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java
index 37ebba815..901c9fc1e 100644
--- a/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java
+++ b/src/main/java/fr/xephi/authme/settings/EnumSetProperty.java
@@ -12,6 +12,8 @@ import static com.google.common.collect.Sets.newHashSet;
/**
* Property whose value is a set of entries of a given enum.
+ *
+ * @param the enum type
*/
public class EnumSetProperty> extends Property> {
diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java
index d287e140c..980f2adde 100644
--- a/src/main/java/fr/xephi/authme/settings/Settings.java
+++ b/src/main/java/fr/xephi/authme/settings/Settings.java
@@ -19,7 +19,6 @@ import static fr.xephi.authme.util.FileUtils.copyFileFromResource;
public class Settings extends SettingsManager {
private final File pluginFolder;
- private String[] welcomeMessage;
private String passwordEmailMessage;
private String recoveryCodeEmailMessage;
@@ -56,19 +55,9 @@ public class Settings extends SettingsManager {
return recoveryCodeEmailMessage;
}
- /**
- * Return the lines to output after an in-game registration.
- *
- * @return The welcome message
- */
- public String[] getWelcomeMessage() {
- return welcomeMessage;
- }
-
private void loadSettingsFromFiles() {
passwordEmailMessage = readFile("email.html");
recoveryCodeEmailMessage = readFile("recovery_code_email.html");
- welcomeMessage = readFile("welcome.txt").split("\\n");
}
@Override
diff --git a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
index aac60dde5..ade6833da 100644
--- a/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
+++ b/src/main/java/fr/xephi/authme/settings/SettingsMigrationService.java
@@ -71,6 +71,7 @@ public class SettingsMigrationService extends PlainMigrationService {
| hasOldHelpHeaderProperty(resource)
| hasSupportOldPasswordProperty(resource)
| convertToRegistrationType(resource)
+ | mergeAndMovePermissionGroupSettings(resource)
|| hasDeprecatedProperties(resource);
}
@@ -81,7 +82,8 @@ public class SettingsMigrationService extends PlainMigrationService {
"VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite",
"Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping",
"settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole",
- "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole"};
+ "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole",
+ "settings.sessions.sessionExpireOnIpChange"};
for (String deprecatedPath : deprecatedProperties) {
if (resource.contains(deprecatedPath)) {
return true;
@@ -251,6 +253,26 @@ public class SettingsMigrationService extends PlainMigrationService {
return true;
}
+ private static boolean mergeAndMovePermissionGroupSettings(PropertyResource resource) {
+ boolean performedChanges;
+
+ // We have two old settings replaced by only one: move the first non-empty one
+ Property oldUnloggedInGroup = newProperty("settings.security.unLoggedinGroup", "");
+ Property oldRegisteredGroup = newProperty("GroupOptions.RegisteredPlayerGroup", "");
+ if (!oldUnloggedInGroup.getValue(resource).isEmpty()) {
+ performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, resource);
+ } else {
+ performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, resource);
+ }
+
+ // Move paths of other old options
+ performedChanges |= moveProperty(newProperty("GroupOptions.UnregisteredPlayerGroup", ""),
+ PluginSettings.UNREGISTERED_GROUP, resource);
+ performedChanges |= moveProperty(newProperty("permission.EnablePermissionCheck", false),
+ PluginSettings.ENABLE_PERMISSION_CHECK, resource);
+ return performedChanges;
+ }
+
/**
* Checks for an old property path and moves it to a new path if present.
*
@@ -264,9 +286,10 @@ public class SettingsMigrationService extends PlainMigrationService {
Property newProperty,
PropertyResource resource) {
if (resource.contains(oldProperty.getPath())) {
- ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath());
- if (!resource.contains(newProperty.getPath())) {
- ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath());
+ if (resource.contains(newProperty.getPath())) {
+ ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath());
+ } else {
+ ConsoleLogger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath());
resource.setValue(newProperty.getPath(), oldProperty.getValue(resource));
}
return true;
diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
index ac1742fa6..26f7d03b9 100644
--- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
+++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
@@ -1,10 +1,9 @@
package fr.xephi.authme.settings;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.datasource.DataSource;
-import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.util.FileUtils;
@@ -43,11 +42,9 @@ public class SpawnLoader implements Reloadable {
* @param pluginFolder The AuthMe data folder
* @param settings The setting instance
* @param pluginHookService The plugin hooks instance
- * @param dataSource The plugin auth database instance
*/
@Inject
- SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService,
- DataSource dataSource) {
+ 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");
diff --git a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java
new file mode 100644
index 000000000..60666f7a3
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java
@@ -0,0 +1,97 @@
+package fr.xephi.authme.settings;
+
+import fr.xephi.authme.ConsoleLogger;
+import fr.xephi.authme.data.auth.PlayerCache;
+import fr.xephi.authme.initialization.DataFolder;
+import fr.xephi.authme.initialization.Reloadable;
+import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.GeoIpService;
+import fr.xephi.authme.util.PlayerUtils;
+import fr.xephi.authme.util.lazytags.Tag;
+import fr.xephi.authme.util.lazytags.TagReplacer;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static fr.xephi.authme.util.FileUtils.copyFileFromResource;
+import static fr.xephi.authme.util.lazytags.TagBuilder.createTag;
+
+/**
+ * Configuration for the welcome message (welcome.txt).
+ */
+public class WelcomeMessageConfiguration implements Reloadable {
+
+ @DataFolder
+ @Inject
+ private File pluginFolder;
+
+ @Inject
+ private Server server;
+
+ @Inject
+ private GeoIpService geoIpService;
+
+ @Inject
+ private BukkitService bukkitService;
+
+ @Inject
+ private PlayerCache playerCache;
+
+ /** List of all supported tags for the welcome message. */
+ private final List> availableTags = Arrays.asList(
+ createTag("&", () -> "\u00a7"),
+ createTag("{PLAYER}", pl -> pl.getName()),
+ createTag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())),
+ createTag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())),
+ createTag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)),
+ createTag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())),
+ createTag("{WORLD}", pl -> pl.getWorld().getName()),
+ createTag("{SERVER}", () -> server.getServerName()),
+ createTag("{VERSION}", () -> server.getBukkitVersion()),
+ createTag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl))));
+
+ private TagReplacer messageSupplier;
+
+ @PostConstruct
+ @Override
+ public void reload() {
+ List welcomeMessage = readWelcomeFile();
+ messageSupplier = TagReplacer.newReplacer(availableTags, welcomeMessage);
+ }
+
+ /**
+ * Returns the welcome message for the given player.
+ *
+ * @param player the player for whom the welcome message should be prepared
+ * @return the welcome message
+ */
+ public List getWelcomeMessage(Player player) {
+ return messageSupplier.getAdaptedMessages(player);
+ }
+
+ /**
+ * @return the lines of the welcome message file
+ */
+ private List readWelcomeFile() {
+ File welcomeFile = new File(pluginFolder, "welcome.txt");
+ if (copyFileFromResource(welcomeFile, "welcome.txt")) {
+ try {
+ return Files.readAllLines(welcomeFile.toPath(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ ConsoleLogger.logException("Failed to read welcome.txt file:", e);
+ }
+ } else {
+ ConsoleLogger.warning("Failed to copy welcome.txt from JAR");
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
index bc0abf38a..f996640c9 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
@@ -5,13 +5,21 @@ import ch.jalu.configme.resource.YamlFileResource;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.util.FileUtils;
+import fr.xephi.authme.util.PlayerUtils;
+import fr.xephi.authme.util.lazytags.Tag;
+import fr.xephi.authme.util.lazytags.WrappedTagReplacer;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.io.File;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
+import static fr.xephi.authme.util.lazytags.TagBuilder.createTag;
+
/**
* Manages configurable commands to be run when various events occur.
*/
@@ -19,15 +27,20 @@ public class CommandManager implements Reloadable {
private final File dataFolder;
private final BukkitService bukkitService;
+ private final GeoIpService geoIpService;
private final CommandMigrationService commandMigrationService;
+ private final List> availableTags = buildAvailableTags();
- private CommandConfig commandConfig;
+ private WrappedTagReplacer onJoinCommands;
+ private WrappedTagReplacer onLoginCommands;
+ private WrappedTagReplacer onRegisterCommands;
@Inject
- CommandManager(@DataFolder File dataFolder, BukkitService bukkitService,
+ CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, GeoIpService geoIpService,
CommandMigrationService commandMigrationService) {
this.dataFolder = dataFolder;
this.bukkitService = bukkitService;
+ this.geoIpService = geoIpService;
this.commandMigrationService = commandMigrationService;
reload();
}
@@ -38,7 +51,7 @@ public class CommandManager implements Reloadable {
* @param player the joining player
*/
public void runCommandsOnJoin(Player player) {
- executeCommands(player, commandConfig.getOnJoin());
+ executeCommands(player, onJoinCommands.getAdaptedItems(player));
}
/**
@@ -47,7 +60,7 @@ public class CommandManager implements Reloadable {
* @param player the player who has registered
*/
public void runCommandsOnRegister(Player player) {
- executeCommands(player, commandConfig.getOnRegister());
+ executeCommands(player, onRegisterCommands.getAdaptedItems(player));
}
/**
@@ -56,12 +69,12 @@ public class CommandManager implements Reloadable {
* @param player the player that logged in
*/
public void runCommandsOnLogin(Player player) {
- executeCommands(player, commandConfig.getOnLogin());
+ executeCommands(player, onLoginCommands.getAdaptedItems(player));
}
- private void executeCommands(Player player, Map commands) {
- for (Command command : commands.values()) {
- final String execution = command.getCommand().replace("%p", player.getName());
+ private void executeCommands(Player player, List commands) {
+ for (Command command : commands) {
+ final String execution = command.getCommand();
if (Executor.CONSOLE.equals(command.getExecutor())) {
bukkitService.dispatchConsoleCommand(execution);
} else {
@@ -77,8 +90,22 @@ public class CommandManager implements Reloadable {
SettingsManager settingsManager = new SettingsManager(
new YamlFileResource(file), commandMigrationService, CommandSettingsHolder.class);
- commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS);
+ CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS);
+ onJoinCommands = newReplacer(commandConfig.getOnJoin());
+ onLoginCommands = newReplacer(commandConfig.getOnLogin());
+ onRegisterCommands = newReplacer(commandConfig.getOnRegister());
}
+ private WrappedTagReplacer newReplacer(Map commands) {
+ return new WrappedTagReplacer<>(availableTags, commands.values(), Command::getCommand,
+ (cmd, text) -> new Command(text, cmd.getExecutor()));
+ }
+ private List