hamcrest-core
diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java
index a968f41f..5b21358f 100644
--- a/src/main/java/fr/xephi/authme/AuthMe.java
+++ b/src/main/java/fr/xephi/authme/AuthMe.java
@@ -14,6 +14,7 @@ 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.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener;
@@ -27,19 +28,18 @@ import fr.xephi.authme.permission.PermissionsSystemType;
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 +72,6 @@ public class AuthMe extends JavaPlugin {
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
- private GeoIpService geoIpService;
- private PlayerCache playerCache;
/**
* Constructor.
@@ -139,6 +137,7 @@ public class AuthMe extends JavaPlugin {
initialize();
} catch (Exception e) {
ConsoleLogger.logException("Aborting initialization of AuthMe:", e);
+ OnStartupTasks.displayLegacyJarHint(e);
stopOrUnload();
return;
}
@@ -197,11 +196,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())
+ .addDefaultHandlers("fr.xephi.authme")
+ .create();
injector.register(AuthMe.class, this);
injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager());
@@ -242,14 +249,13 @@ public class AuthMe extends JavaPlugin {
*/
protected 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 +276,12 @@ 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");
+ }
}
/**
@@ -344,24 +356,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 b0167459..9d18820c 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 d05fbe6b..75e746e6 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 d4f34b2c..2193f64c 100644
--- a/src/main/java/fr/xephi/authme/api/NewAPI.java
+++ b/src/main/java/fr/xephi/authme/api/NewAPI.java
@@ -24,6 +24,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;
diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java
index b6c006f9..e195c475 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 394b6f14..083d8a53 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 7e01e0e3..98030c96 100644
--- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java
+++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
@@ -24,6 +24,7 @@ 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;
@@ -37,6 +38,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;
@@ -298,6 +300,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)
diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java
index 8e2b1bdc..33003c3a 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/executable/authme/ConverterCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ConverterCommand.java
index efa27ff0..e1c0661c 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;
@@ -13,6 +12,7 @@ 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.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() {
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 037846aa..5a3604f1 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
@@ -11,10 +11,10 @@ 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;
/**
@@ -44,8 +44,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 +56,10 @@ public class ReloadCommand implements ExecutableCommand {
}
private void performReloadOnServices() {
- Collection reloadables = injector.retrieveAllOfType(Reloadable.class);
- for (Reloadable reloadable : reloadables) {
- reloadable.reload();
- }
+ injector.retrieveAllOfType(Reloadable.class)
+ .forEach(r -> r.reload());
- Collection settingsDependents = injector.retrieveAllOfType(SettingsDependent.class);
- for (SettingsDependent dependent : settingsDependents) {
- dependent.reload(settings);
- }
+ injector.retrieveAllOfType(SettingsDependent.class)
+ .forEach(s -> s.reload(settings));
}
}
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 00000000..05de8ab1
--- /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/DebugCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java
new file mode 100644
index 00000000..510ac9a1
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/DebugCommand.java
@@ -0,0 +1,58 @@
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Debug command main.
+ */
+public class DebugCommand implements ExecutableCommand {
+
+ @Inject
+ private Factory debugSectionFactory;
+
+ private Set> sectionClasses =
+ ImmutableSet.of(PermissionGroups.class, TestEmailSender.class, CountryLookup.class);
+
+ 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 HashMap<>();
+ for (Class extends DebugSection> sectionClass : sectionClasses) {
+ 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 00000000..1f038983
--- /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/PermissionGroups.java b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java
new file mode 100644
index 00000000..e3876ff0
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/PermissionGroups.java
@@ -0,0 +1,40 @@
+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)));
+ }
+ }
+}
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 00000000..b0abcd55
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/debug/TestEmailSender.java
@@ -0,0 +1,100 @@
+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 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 (email.contains("@")) {
+ 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 c5756cda..d7150c89 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,7 +3,6 @@ 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.message.MessageKey;
import fr.xephi.authme.service.CommonService;
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 5dc16ac6..b3fb3b62 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,24 +5,31 @@ 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.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.service.CommonService;
import fr.xephi.authme.service.RecoveryCodeService;
+import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
+import fr.xephi.authme.util.expiring.Duration;
+import fr.xephi.authme.util.expiring.ExpiringSet;
import org.bukkit.entity.Player;
+import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
* Command for password recovery by email.
*/
-public class RecoverEmailCommand extends PlayerCommand {
+public class RecoverEmailCommand extends PlayerCommand implements Reloadable {
@Inject
private PasswordSecurity passwordSecurity;
@@ -37,17 +44,28 @@ public class RecoverEmailCommand extends PlayerCommand {
private PlayerCache playerCache;
@Inject
- private SendMailSSL sendMailSsl;
+ private EmailService emailService;
@Inject
private RecoveryCodeService recoveryCodeService;
+ @Inject
+ private Messages messages;
+
+ private ExpiringSet emailCooldown;
+
+ @PostConstruct
+ private void initEmailCooldownSet() {
+ emailCooldown = new ExpiringSet<>(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ }
+
@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;
@@ -78,15 +96,29 @@ public class RecoverEmailCommand extends PlayerCommand {
processRecoveryCode(player, arguments.get(1), email);
}
} else {
- generateAndSendNewPassword(player, email);
+ boolean maySendMail = checkEmailCooldown(player);
+ if (maySendMail) {
+ generateAndSendNewPassword(player, email);
+ }
}
}
+ @Override
+ public void reload() {
+ emailCooldown.setExpiration(
+ commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
+ }
+
private void createAndSendRecoveryCode(Player player, String email) {
+ if (!checkEmailCooldown(player)) {
+ return;
+ }
+
String recoveryCode = recoveryCodeService.generateCode(player.getName());
- boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode);
+ 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);
}
@@ -108,11 +140,22 @@ public class RecoverEmailCommand extends PlayerCommand {
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
dataSource.updatePassword(name, hashNew);
- boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass);
+ 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);
}
}
+
+ 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;
+ }
}
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 ac4bb4bd..6a0a590c 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,7 +2,7 @@ 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;
@@ -37,7 +37,7 @@ public class RegisterCommand extends PlayerCommand {
private CommonService commonService;
@Inject
- private SendMailSSL sendMailSsl;
+ private EmailService emailService;
@Inject
private ValidationService validationService;
@@ -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());
diff --git a/src/main/java/fr/xephi/authme/data/CaptchaManager.java b/src/main/java/fr/xephi/authme/data/CaptchaManager.java
index 36f33a3c..b328d545 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 f86f5421..512941db 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 e5d31ed1..1d55fa8f 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/limbo/LimboCache.java b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java
index 893aba22..26883ca9 100644
--- a/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboCache.java
@@ -1,11 +1,8 @@
package fr.xephi.authme.data.limbo;
-import fr.xephi.authme.data.backup.LimboPlayerStorage;
+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.PluginSettings;
-import fr.xephi.authme.util.StringUtils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -23,14 +20,11 @@ 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;
+ LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) {
this.permissionsManager = permissionsManager;
this.spawnLoader = spawnLoader;
this.limboPlayerStorage = limboPlayerStorage;
@@ -52,6 +46,7 @@ public class LimboCache {
if (permissionsManager.hasGroupSupport()) {
playerGroup = permissionsManager.getPrimaryGroup(player);
}
+ ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup);
if (limboPlayerStorage.hasData(player)) {
LimboPlayer cache = limboPlayerStorage.readData(player);
@@ -84,15 +79,14 @@ public class LimboCache {
float walkSpeed = data.getWalkSpeed();
float flySpeed = data.getFlySpeed();
// Reset the speed value if it was 0
- if(walkSpeed < 0.01f) {
- walkSpeed = 0.2f;
+ if (walkSpeed < 0.01f) {
+ walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED;
}
- if(flySpeed < 0.01f) {
- flySpeed = 0.2f;
+ if (flySpeed < 0.01f) {
+ flySpeed = LimboPlayer.DEFAULT_FLY_SPEED;
}
player.setWalkSpeed(walkSpeed);
player.setFlySpeed(flySpeed);
- restoreGroup(player, data.getGroup());
data.clearTasks();
}
}
@@ -154,11 +148,4 @@ public class LimboCache {
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 b551deed..45fcd7ad 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;
diff --git a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java
similarity index 98%
rename from src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java
rename to src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java
index 1b227028..1f077d2a 100644
--- a/src/main/java/fr/xephi/authme/data/backup/LimboPlayerStorage.java
+++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayerStorage.java
@@ -1,4 +1,4 @@
-package fr.xephi.authme.data.backup;
+package fr.xephi.authme.data.limbo;
import com.google.common.io.Files;
import com.google.gson.Gson;
@@ -10,11 +10,10 @@ 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.settings.SpawnLoader;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.Location;
@@ -149,8 +148,8 @@ public class LimboPlayerStorage {
String group = "";
boolean operator = false;
boolean canFly = false;
- float walkSpeed = 0.2f;
- float flySpeed = 0.2f;
+ float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED;
+ float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED;
JsonElement e;
if ((e = jsonObject.getAsJsonObject("location")) != null) {
diff --git a/src/main/java/fr/xephi/authme/datasource/Columns.java b/src/main/java/fr/xephi/authme/datasource/Columns.java
index b6d732cd..f984007e 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 a4ddbe1c..4e6937d4 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -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,10 +99,13 @@ 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);
}
@@ -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,8 +514,9 @@ 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);
@@ -490,6 +528,39 @@ public class MySQL implements DataSource {
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();
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 d2be7804..8ca50625 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 a52b216b..1c67061f 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 eb9d904c..42a8ecf1 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,19 +41,17 @@ 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("=")) {
@@ -63,20 +62,20 @@ public class RakamakConverter implements Converter {
}
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/xAuthConverter.java b/src/main/java/fr/xephi/authme/datasource/converter/xAuthConverter.java
index 27fa6c2e..af4d5beb 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;
@@ -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 1e786244..11e9d6af 100644
--- a/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java
+++ b/src/main/java/fr/xephi/authme/initialization/OnShutdownPlayerSaver.java
@@ -2,7 +2,7 @@ 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.LimboPlayerStorage;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.PluginHookService;
diff --git a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
index 0dd42181..8767e88a 100644
--- a/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
+++ b/src/main/java/fr/xephi/authme/initialization/OnStartupTasks.java
@@ -1,11 +1,13 @@
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 org.bstats.Metrics;
import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.service.BukkitService;
@@ -18,10 +20,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 +45,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 +119,23 @@ 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/");
+ }
+ }
+ }
}
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 00000000..0f4ae62a
--- /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 00000000..04c11c68
--- /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/listener/OnJoinVerifier.java b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
index e3ff4ec5..06a8761a 100644
--- a/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
+++ b/src/main/java/fr/xephi/authme/listener/OnJoinVerifier.java
@@ -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/protocollib/InventoryPacketAdapter.java b/src/main/java/fr/xephi/authme/listener/protocollib/InventoryPacketAdapter.java
index 48cf1868..05150765 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 4476f80a..daf68638 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 00000000..cb7c86de
--- /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
index a782d3a1..ece40900 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;
@@ -34,16 +26,8 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
*/
public class SendMailSSL {
- private final File dataFolder;
- private final String serverName;
- private final Settings settings;
-
@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,13 +58,15 @@ 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) {
+ 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
@@ -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 a0d4e947..5d19e8f4 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 987caecc..5b809b2c 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 6cc57480..e8c3939a 100644
--- a/src/main/java/fr/xephi/authme/message/MessageKey.java
+++ b/src/main/java/fr/xephi/authme/message/MessageKey.java
@@ -17,7 +17,7 @@ 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. */
@@ -26,7 +26,7 @@ public enum MessageKey {
/** 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 +56,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 +95,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 +113,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 +122,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 +143,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! */
@@ -224,8 +224,36 @@ 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! Use "/email recovery [email]" to generate a new one */
+ INCORRECT_RECOVERY_CODE("recovery_code_incorrect"),
+
+ /** 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 86fa2db6..d7fdcec8 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 605283ac..7d46daf8 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 537c3ebc..c7a3b343 100644
--- a/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java
+++ b/src/main/java/fr/xephi/authme/permission/AuthGroupHandler.java
@@ -5,17 +5,24 @@ import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
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 {
@@ -28,7 +35,6 @@ public class AuthGroupHandler implements Reloadable {
@Inject
private LimboCache limboCache;
- 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(limboCache.getPlayerData(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 9ab2a370..dfedf8ee 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 7ce0b3e5..497c99ab 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;
/**
*
@@ -255,12 +255,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 +289,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 +307,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 +322,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 +338,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 +373,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 +389,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 aaeb0eea..2160eea5 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 9f52214b..849ecd65 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 71c7a287..36f6497c 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 {
@@ -48,7 +49,9 @@ public interface PermissionHandler {
* @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.
*/
- 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.
@@ -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 c215b8d1..acae466c 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 b11d0000..9b4550d8 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 8955569e..79badc53 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 498ba4a9..c86863f7 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/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
index dab9cdc2..73bef42a 100644
--- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
+++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java
@@ -1,6 +1,5 @@
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;
@@ -8,30 +7,32 @@ import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboCache;
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 +40,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;
@@ -71,6 +72,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
@Inject
private CommandManager commandManager;
+ @Inject
+ private ValidationService validationService;
+
AsynchronousJoin() {
}
@@ -91,13 +95,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);
}
}
});
@@ -112,7 +116,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
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,12 +134,14 @@ 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 {
@@ -178,36 +184,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
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;
- }
-
/**
* Checks whether the maximum number of accounts has been exceeded for the given IP address (according to
* settings and permissions). If this is the case, the player is kicked.
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 db85b858..255b36b0 100644
--- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
@@ -12,7 +12,6 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
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;
@@ -46,9 +45,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
@Inject
private CommonService service;
- @Inject
- private PermissionsManager permissionsManager;
-
@Inject
private PlayerCache playerCache;
@@ -269,8 +265,8 @@ public class AsynchronousLogin implements AsynchronousProcess {
}
bukkitService.dispatchConsoleCommand(command
- .replaceAll("%playername%", player.getName())
- .replaceAll("%playerip%", ip)
+ .replace("%playername%", player.getName())
+ .replace("%playerip%", ip)
);
}
@@ -300,10 +296,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 +319,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 d64bda77..70402e53 100644
--- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
@@ -1,6 +1,5 @@
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;
@@ -8,28 +7,26 @@ 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.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;
@@ -39,9 +36,6 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
@Inject
private BukkitService bukkitService;
- @Inject
- private PluginManager pluginManager;
-
@Inject
private TeleportationService teleportationService;
@@ -52,14 +46,17 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
private CommandManager commandManager;
@Inject
- private Settings settings;
+ private CommonService commonService;
+
+ @Inject
+ private WelcomeMessageConfiguration welcomeMessageConfiguration;
ProcessSyncPlayerLogin() {
}
private void restoreInventory(Player player) {
RestoreInventoryEvent event = new RestoreInventoryEvent(player);
- pluginManager.callEvent(event);
+ bukkitService.callEvent(event);
if (!event.isCancelled()) {
player.updateInventory();
}
@@ -76,8 +73,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
// do we really need to use location from database for now?
// because LimboCache#restoreData teleport player to last location.
}
+ commonService.setGroup(player, AuthGroupType.LOGGED_IN);
- if (settings.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
+ if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
restoreInventory(player);
}
@@ -94,7 +92,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
}
}
- if (settings.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
+ if (commonService.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
player.removePotionEffect(PotionEffectType.BLINDNESS);
}
@@ -103,15 +101,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/ProcessSynchronousPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
index 17710df4..18229146 100644
--- a/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
+++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSynchronousPlayerLogout.java
@@ -77,7 +77,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess {
}
// Set player's data to unauthenticated
- service.setGroup(player, AuthGroupType.NOT_LOGGED_IN);
+ service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
player.setOp(false);
player.setAllowFlight(false);
// Remove speed
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 73db67f8..47bfb912 100644
--- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java
+++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncronousPlayerQuit.java
@@ -1,6 +1,6 @@
package fr.xephi.authme.process.quit;
-import fr.xephi.authme.data.backup.LimboPlayerStorage;
+import fr.xephi.authme.data.limbo.LimboPlayerStorage;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.process.SynchronousProcess;
import org.bukkit.entity.Player;
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 aea1b4be..cebbcdcb 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncEmailRegister.java
@@ -3,9 +3,8 @@ package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger;
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.service.CommonService;
import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
@@ -25,12 +24,10 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
}
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);
+ final String name = player.getName().toLowerCase();
limboPlayerTaskManager.registerTimeoutTask(player);
limboPlayerTaskManager.registerMessageTask(name, true);
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 fae428d0..0b06df61 100644
--- a/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/ProcessSyncPasswordRegister.java
@@ -4,12 +4,11 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboCache;
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;
@@ -56,10 +55,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/EmailRegisterExecutorProvider.java b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java
index 88f8cd60..7a6e702d 100644
--- a/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java
+++ b/src/main/java/fr/xephi/authme/process/register/executors/EmailRegisterExecutorProvider.java
@@ -2,7 +2,7 @@ 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.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.SyncProcessManager;
@@ -15,8 +15,8 @@ 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.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
@@ -34,7 +34,7 @@ class EmailRegisterExecutorProvider {
private CommonService commonService;
@Inject
- private SendMailSSL sendMailSsl;
+ private EmailService emailService;
@Inject
private SyncProcessManager syncProcessManager;
@@ -80,7 +80,7 @@ class EmailRegisterExecutorProvider {
@Override
public void executePostPersistAction() {
- boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password);
+ boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password);
if (couldSendMail) {
syncProcessManager.processSyncEmailRegister(player);
} else {
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
index a79c63c8..9d40bcf8 100644
--- a/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java
+++ b/src/main/java/fr/xephi/authme/process/register/executors/PasswordRegisterExecutorProvider.java
@@ -56,10 +56,10 @@ class PasswordRegisterExecutorProvider {
/** Registration executor for password registration. */
class PasswordRegisterExecutor implements RegistrationExecutor {
- protected final Player player;
+ private final Player player;
private final String password;
private final String email;
- protected HashedPassword hashedPassword;
+ private HashedPassword hashedPassword;
/**
* Constructor.
@@ -105,6 +105,14 @@ class PasswordRegisterExecutorProvider {
}
syncProcessManager.processSyncPasswordRegister(player);
}
+
+ protected Player getPlayer() {
+ return player;
+ }
+
+ protected HashedPassword getHashedPassword() {
+ return hashedPassword;
+ }
}
/** Executor for password registration via API call. */
@@ -147,8 +155,9 @@ class PasswordRegisterExecutorProvider {
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);
+ String hash = getHashedPassword().getHash();
+ String qrCodeUrl = TwoFactor.getQRBarcodeURL(getPlayer().getName(), Bukkit.getIp(), hash);
+ commonService.send(getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl);
}
}
diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
index 8549b388..d2947108 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/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java
index 7d4b3d95..d0dacda4 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/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java
index 698979d8..23101e22 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/service/AntiBotService.java b/src/main/java/fr/xephi/authme/service/AntiBotService.java
index 91f80041..90c62a24 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 14cf0dcb..e945cebd 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;
@@ -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);
diff --git a/src/main/java/fr/xephi/authme/service/CommonService.java b/src/main/java/fr/xephi/authme/service/CommonService.java
index c181f83b..d2381a45 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/RecoveryCodeService.java b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
index e7fa37ad..cae8aaa7 100644
--- a/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
+++ b/src/main/java/fr/xephi/authme/service/RecoveryCodeService.java
@@ -1,38 +1,38 @@
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 javax.inject.Inject;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
-import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
/**
* 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 int recoveryCodeLength;
- private long recoveryCodeExpirationMillis;
+ private int recoveryCodeExpiration;
@Inject
RecoveryCodeService(Settings settings) {
- reload(settings);
+ recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
+ recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID);
+ recoveryCodes = new ExpiringMap<>(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 +43,7 @@ public class RecoveryCodeService implements SettingsDependent {
*/
public String generateCode(String player) {
String code = RandomStringUtils.generateHex(recoveryCodeLength);
- recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis));
+ recoveryCodes.put(player, code);
return code;
}
@@ -55,11 +55,8 @@ 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);
+ return storedCode != null && storedCode.equals(code);
}
/**
@@ -74,26 +71,12 @@ public class RecoveryCodeService implements SettingsDependent {
@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(RECOVERY_CODE_HOURS_VALID);
+ 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();
}
-
}
diff --git a/src/main/java/fr/xephi/authme/service/ValidationService.java b/src/main/java/fr/xephi/authme/service/ValidationService.java
index 7905b367..9511c6e3 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/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java
index d287e140..980f2add 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 aac60dde..ade6833d 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 ac1742fa..26f7d03b 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 00000000..60666f7a
--- /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 bc0abf38..f996640c 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> buildAvailableTags() {
+ return Arrays.asList(
+ createTag("%p", pl -> pl.getName()),
+ createTag("%nick", pl -> pl.getDisplayName()),
+ createTag("%ip", pl -> PlayerUtils.getPlayerIp(pl)),
+ createTag("%country", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl))));
+ }
}
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
index 11557c09..7104cee2 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
@@ -24,7 +24,12 @@ public final class CommandSettingsHolder implements SettingsHolder {
public static Map sectionComments() {
String[] comments = {
"This configuration file allows you to execute commands on various events.",
- "%p in commands will be replaced with the player name.",
+ "Supported placeholders in commands:",
+ " %p is replaced with the player name.",
+ " %nick is replaced with the player's nick name",
+ " %ip is replaced with the player's IP address",
+ " %country is replaced with the player's country",
+ "",
"For example, if you want to send a welcome message to a player who just registered:",
"onRegister:",
" welcome:",
diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
index 162bf8aa..57bb5941 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
@@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class BackupSettings implements SettingsHolder {
+public final class BackupSettings implements SettingsHolder {
@Comment("Enable or disable automatic backup")
public static final Property ENABLED =
diff --git a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java
index ae289e54..d2b34c9a 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/ConverterSettings.java
@@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class ConverterSettings implements SettingsHolder {
+public final class ConverterSettings implements SettingsHolder {
@Comment("Rakamak file name")
public static final Property RAKAMAK_FILE_NAME =
diff --git a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
index ed877f87..1e4697bf 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/DatabaseSettings.java
@@ -7,10 +7,10 @@ import fr.xephi.authme.datasource.DataSourceType;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class DatabaseSettings implements SettingsHolder {
+public final class DatabaseSettings implements SettingsHolder {
@Comment({"What type of database do you want to use?",
- "Valid values: sqlite, mysql"})
+ "Valid values: SQLITE, MYSQL"})
public static final Property BACKEND =
newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE);
@@ -26,11 +26,15 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property MYSQL_PORT =
newProperty("DataSource.mySQLPort", "3306");
- @Comment("Username about Database Connection Infos")
+ @Comment("Connect to MySQL database over SSL")
+ public static final Property MYSQL_USE_SSL =
+ newProperty("DataSource.mySQLUseSSL", true);
+
+ @Comment("Username to connect to the MySQL database")
public static final Property MYSQL_USERNAME =
newProperty("DataSource.mySQLUsername", "authme");
- @Comment("Password about Database Connection Infos")
+ @Comment("Password to connect to the MySQL database")
public static final Property MYSQL_PASSWORD =
newProperty("DataSource.mySQLPassword", "12345");
@@ -58,10 +62,6 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property MYSQL_COL_PASSWORD =
newProperty("DataSource.mySQLColumnPassword", "password");
- @Comment("Request mysql over SSL")
- public static final Property MYSQL_USE_SSL =
- newProperty("DataSource.mySQLUseSSL", true);
-
@Comment("Column for storing players passwords salts")
public static final Property MYSQL_COL_SALT =
newProperty("ExternalBoardOptions.mySQLColumnSalt", "");
diff --git a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java
index 561ce511..f7522b94 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/EmailSettings.java
@@ -9,7 +9,7 @@ import java.util.List;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class EmailSettings implements SettingsHolder {
+public final class EmailSettings implements SettingsHolder {
@Comment("Email SMTP server host")
public static final Property SMTP_HOST =
@@ -19,6 +19,10 @@ public class EmailSettings implements SettingsHolder {
public static final Property SMTP_PORT =
newProperty("Email.mailPort", 465);
+ @Comment("Only affects port 25: enable TLS/STARTTLS?")
+ public static final Property PORT25_USE_TLS =
+ newProperty("Email.useTls", true);
+
@Comment("Email account which sends the mails")
public static final Property MAIL_ACCOUNT =
newProperty("Email.mailAccount", "");
diff --git a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java
index b1eaa222..e752057c 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/HooksSettings.java
@@ -9,7 +9,7 @@ import java.util.List;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class HooksSettings implements SettingsHolder {
+public final class HooksSettings implements SettingsHolder {
@Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property MULTIVERSE =
@@ -54,17 +54,22 @@ public class HooksSettings implements SettingsHolder {
public static final Property PHPBB_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2);
+ @Comment("IP Board table prefix defined during the IP Board installation process")
+ public static final Property IPB_TABLE_PREFIX =
+ newProperty("ExternalBoardOptions.IPBTablePrefix", "ipb_");
+
+ @Comment("IP Board default group ID; 3 is the default registered group defined by IP Board")
+ public static final Property IPB_ACTIVATED_GROUP_ID =
+ newProperty("ExternalBoardOptions.IPBActivatedGroupId", 3);
+
+ @Comment("XenForo default group ID; 2 is the default registered group defined by Xenforo")
+ public static final Property XF_ACTIVATED_GROUP_ID =
+ newProperty("ExternalBoardOptions.XFActivatedGroupId", 2);
+
@Comment("Wordpress prefix defined during WordPress installation")
public static final Property WORDPRESS_TABLE_PREFIX =
newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_");
- @Comment("Unregistered permission group")
- public static final Property UNREGISTERED_GROUP =
- newProperty("GroupOptions.UnregisteredPlayerGroup", "");
-
- @Comment("Registered permission group")
- public static final Property REGISTERED_GROUP =
- newProperty("GroupOptions.RegisteredPlayerGroup", "");
private HooksSettings() {
}
diff --git a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java
index cfd717eb..d99ffa0b 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/PluginSettings.java
@@ -7,7 +7,7 @@ import fr.xephi.authme.output.LogLevel;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class PluginSettings implements SettingsHolder {
+public final class PluginSettings implements SettingsHolder {
@Comment({
"Do you want to enable the session feature?",
@@ -22,20 +22,11 @@ public class PluginSettings implements SettingsHolder {
@Comment({
"After how many minutes should a session expire?",
- "Remember that sessions will end only after the timeout, and",
- "if the player's IP has changed but the timeout hasn't expired,",
- "the player will be kicked from the server due to invalid session"
+ "A player's session ends after the timeout or if his IP has changed"
})
public static final Property SESSIONS_TIMEOUT =
newProperty("settings.sessions.timeout", 10);
- @Comment({
- "Should the session expire if the player tries to log in with",
- "another IP address?"
- })
- public static final Property SESSIONS_EXPIRE_ON_IP_CHANGE =
- newProperty("settings.sessions.sessionExpireOnIpChange", true);
-
@Comment({
"Message language, available languages:",
"https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md"
@@ -44,13 +35,33 @@ public class PluginSettings implements SettingsHolder {
newProperty("settings.messagesLanguage", "en");
@Comment({
- "Take care with this option; if you want",
- "to use group switching of AuthMe",
- "for unloggedIn players, set this setting to true.",
- "Default is false."
+ "Enables switching a player to defined permission groups before they log in.",
+ "See below for a detailed explanation."
})
public static final Property ENABLE_PERMISSION_CHECK =
- newProperty("permission.EnablePermissionCheck", false);
+ newProperty("GroupOptions.enablePermissionCheck", false);
+
+ @Comment({
+ "This is a very important option: if a registered player joins the server",
+ "AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits.",
+ "You can set up your permission plugin with this special group to have no permissions,",
+ "or only permission to chat (or permission to send private messages etc.).",
+ "The better way is to set up this group with few permissions, so if a player",
+ "tries to exploit an account they can do only what you've defined for the group.",
+ "After login, the player will be moved to his correct permissions group!",
+ "Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'",
+ "Otherwise your group will be wiped and the player will join in the default group []!",
+ "Example: registeredPlayerGroup: 'NotLogged'"
+ })
+ public static final Property REGISTERED_GROUP =
+ newProperty("GroupOptions.registeredPlayerGroup", "");
+
+ @Comment({
+ "Similar to above, unregistered players can be set to the following",
+ "permissions group"
+ })
+ public static final Property UNREGISTERED_GROUP =
+ newProperty("GroupOptions.unregisteredPlayerGroup", "");
@Comment({
"Log level: INFO, FINE, DEBUG. Use INFO for general messages,",
diff --git a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java
index 3a19a70e..2bae7179 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/ProtectionSettings.java
@@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class ProtectionSettings implements SettingsHolder {
+public final class ProtectionSettings implements SettingsHolder {
@Comment("Enable some servers protection (country based login, antibot)")
public static final Property ENABLE_PROTECTION =
diff --git a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
index 0cfa029a..2c62454c 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/PurgeSettings.java
@@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class PurgeSettings implements SettingsHolder {
+public final class PurgeSettings implements SettingsHolder {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
public static final Property USE_AUTO_PURGE =
diff --git a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java
index 38615b78..7d87e77b 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/RegistrationSettings.java
@@ -8,7 +8,7 @@ import fr.xephi.authme.process.register.RegistrationType;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class RegistrationSettings implements SettingsHolder {
+public final class RegistrationSettings implements SettingsHolder {
@Comment("Enable registration on the server?")
public static final Property IS_ENABLED =
@@ -42,7 +42,8 @@ public class RegistrationSettings implements SettingsHolder {
"EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address"
})
public static final Property REGISTER_SECOND_ARGUMENT =
- newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", RegisterSecondaryArgument.CONFIRMATION);
+ newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg",
+ RegisterSecondaryArgument.CONFIRMATION);
@Comment({
"Do we force kick a player after a successful registration?",
diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
index 4e282728..40de8ca6 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
@@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class RestrictionSettings implements SettingsHolder {
+public final class RestrictionSettings implements SettingsHolder {
@Comment({
"Can not authenticated players chat?",
@@ -81,9 +81,13 @@ public class RestrictionSettings implements SettingsHolder {
"Example:",
" AllowedRestrictedUser:",
" - playername;127.0.0.1"})
- public static final Property> ALLOWED_RESTRICTED_USERS =
+ public static final Property> RESTRICTED_USERS =
newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser");
+ @Comment("Ban unknown IPs trying to log in with a restricted username?")
+ public static final Property BAN_UNKNOWN_IP =
+ newProperty("settings.restrictions.banUnsafedIP", false);
+
@Comment("Should unregistered players be kicked immediately?")
public static final Property KICK_NON_REGISTERED =
newProperty("settings.restrictions.kickNonRegistered", false);
@@ -115,7 +119,7 @@ public class RestrictionSettings implements SettingsHolder {
public static final Property TIMEOUT =
newProperty("settings.restrictions.timeout", 30);
- @Comment("Regex syntax of allowed characters in the player name.")
+ @Comment("Regex pattern of allowed characters in the player name.")
public static final Property ALLOWED_NICKNAME_CHARACTERS =
newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*");
@@ -140,10 +144,6 @@ public class RestrictionSettings implements SettingsHolder {
public static final Property DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true);
- @Comment("Ban ip when the ip is not the ip registered in database")
- public static final Property BAN_UNKNOWN_IP =
- newProperty("settings.restrictions.banUnsafedIP", false);
-
@Comment("Spawn priority; values: authme, essentials, multiverse, default")
public static final Property SPAWN_PRIORITY =
newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");
diff --git a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
index 5d3d870c..455af7e8 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/SecuritySettings.java
@@ -12,7 +12,7 @@ import java.util.Set;
import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
-public class SecuritySettings implements SettingsHolder {
+public final class SecuritySettings implements SettingsHolder {
@Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set this to false,",
@@ -40,6 +40,10 @@ public class SecuritySettings implements SettingsHolder {
public static final Property CAPTCHA_LENGTH =
newProperty("Security.captcha.captchaLength", 5);
+ @Comment("Minutes after which login attempts count is reset for a player")
+ public static final Property CAPTCHA_COUNT_MINUTES_BEFORE_RESET =
+ newProperty("Security.captcha.captchaCountReset", 60);
+
@Comment("Minimum length of password")
public static final Property MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5);
@@ -48,22 +52,6 @@ public class SecuritySettings implements SettingsHolder {
public static final Property MAX_PASSWORD_LENGTH =
newProperty("settings.security.passwordMaxLength", 30);
- @Comment({
- "This is a very important option: every time a player joins the server,",
- "if they are registered, AuthMe will switch him to unLoggedInGroup.",
- "This should prevent all major exploits.",
- "You can set up your permission plugin with this special group to have no permissions,",
- "or only permission to chat (or permission to send private messages etc.).",
- "The better way is to set up this group with few permissions, so if a player",
- "tries to exploit an account they can do only what you've defined for the group.",
- "After, a logged in player will be moved to his correct permissions group!",
- "Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'",
- "Otherwise your group will be wiped and the player will join in the default group []!",
- "Example unLoggedinGroup: NotLogged"
- })
- public static final Property UNLOGGEDIN_GROUP =
- newProperty("settings.security.unLoggedinGroup", "unLoggedinGroup");
-
@Comment({
"Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,",
"MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,",
@@ -98,7 +86,8 @@ public class SecuritySettings implements SettingsHolder {
"- 'password'",
"- 'help'"})
public static final Property> UNSAFE_PASSWORDS =
- newLowercaseListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321", "123456789", "help");
+ newLowercaseListProperty("settings.security.unsafePasswords",
+ "123456", "password", "qwerty", "12345", "54321", "123456789", "help");
@Comment("Tempban a user's IP address if they enter the wrong password too many times")
public static final Property TEMPBAN_ON_MAX_LOGINS =
@@ -126,6 +115,13 @@ public class SecuritySettings implements SettingsHolder {
public static final Property RECOVERY_CODE_HOURS_VALID =
newProperty("Security.recoveryCode.validForHours", 4);
+ @Comment({
+ "Seconds a user has to wait for before a password recovery mail may be sent again",
+ "This prevents an attacker from abusing AuthMe's email feature."
+ })
+ public static final Property EMAIL_RECOVERY_COOLDOWN_SECONDS =
+ newProperty("Security.emailRecovery.cooldown", 60);
+
private SecuritySettings() {
}
diff --git a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java
index 7a1d19fa..fe6d2fbf 100644
--- a/src/main/java/fr/xephi/authme/task/purge/PurgeService.java
+++ b/src/main/java/fr/xephi/authme/task/purge/PurgeService.java
@@ -3,20 +3,19 @@ package fr.xephi.authme.task.purge;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.permission.PermissionsManager;
+import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PurgeSettings;
-import fr.xephi.authme.service.BukkitService;
-import fr.xephi.authme.util.CollectionUtils;
+import fr.xephi.authme.util.Utils;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Collection;
import java.util.Set;
-// TODO: move into services. -sgdc3
+import static fr.xephi.authme.util.Utils.logAndSendMessage;
/**
* Initiates purge tasks.
@@ -75,7 +74,7 @@ public class PurgeService {
public void runPurge(CommandSender sender, long until, boolean includeEntriesWithLastLoginZero) {
//todo: note this should may run async because it may executes a SQL-Query
Set toPurge = dataSource.getRecordsToPurge(until, includeEntriesWithLastLoginZero);
- if (CollectionUtils.isEmpty(toPurge)) {
+ if (Utils.isCollectionEmpty(toPurge)) {
logAndSendMessage(sender, "No players to purge");
return;
}
@@ -119,12 +118,4 @@ public class PurgeService {
void executePurge(Collection players, Collection names) {
purgeExecutor.executePurge(players, names);
}
-
- private static void logAndSendMessage(CommandSender sender, String message) {
- ConsoleLogger.info(message);
- // Make sure sender is not console user, which will see the message from ConsoleLogger already
- if (sender != null && !(sender instanceof ConsoleCommandSender)) {
- sender.sendMessage(message);
- }
- }
}
diff --git a/src/main/java/fr/xephi/authme/util/CollectionUtils.java b/src/main/java/fr/xephi/authme/util/CollectionUtils.java
deleted file mode 100644
index 2388fc60..00000000
--- a/src/main/java/fr/xephi/authme/util/CollectionUtils.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package fr.xephi.authme.util;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Utils class for collections.
- */
-public final class CollectionUtils {
-
- // Utility class
- private CollectionUtils() {
- }
-
- /**
- * Get a range from a list based on start and count parameters in a safe way.
- *
- * @param element
- * @param list The List
- * @param start The start index
- * @param count The number of elements to add
- *
- * @return The sublist consisting at most of {@code count} elements (less if the parameters
- * exceed the size of the list)
- */
- public static List getRange(List list, int start, int count) {
- if (start >= list.size() || count <= 0) {
- return new ArrayList<>();
- } else if (start < 0) {
- start = 0;
- }
- int end = Math.min(list.size(), start + count);
- return list.subList(start, end);
- }
-
- /**
- * Get all elements from a list starting from the given index.
- *
- * @param element
- * @param list The List
- * @param start The start index
- *
- * @return The sublist of all elements from index {@code start} and on; empty list
- * if the start index exceeds the list's size
- */
- public static List getRange(List list, int start) {
- if (start >= list.size()) {
- return new ArrayList<>();
- }
- return getRange(list, start, list.size() - start);
- }
-
- /**
- * Null-safe way to check whether a collection is empty or not.
- *
- * @param coll The collection to verify
- * @return True if the collection is null or empty, false otherwise
- */
- public static boolean isEmpty(Collection> coll) {
- return coll == null || coll.isEmpty();
- }
-}
diff --git a/src/main/java/fr/xephi/authme/util/PlayerUtils.java b/src/main/java/fr/xephi/authme/util/PlayerUtils.java
index 7c7302eb..3c4e067b 100644
--- a/src/main/java/fr/xephi/authme/util/PlayerUtils.java
+++ b/src/main/java/fr/xephi/authme/util/PlayerUtils.java
@@ -6,7 +6,7 @@ import org.bukkit.entity.Player;
/**
* Player utilities.
*/
-public class PlayerUtils {
+public final class PlayerUtils {
// Utility class
private PlayerUtils() {
diff --git a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java
index 0ad582ab..2dce3c64 100644
--- a/src/main/java/fr/xephi/authme/util/RandomStringUtils.java
+++ b/src/main/java/fr/xephi/authme/util/RandomStringUtils.java
@@ -8,7 +8,7 @@ import java.util.Random;
*/
public final class RandomStringUtils {
- private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final char[] CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final Random RANDOM = new SecureRandom();
private static final int HEX_MAX_INDEX = 16;
private static final int LOWER_ALPHANUMERIC_INDEX = 36;
@@ -24,7 +24,7 @@ public final class RandomStringUtils {
* @return The random string
*/
public static String generate(int length) {
- return generate(length, LOWER_ALPHANUMERIC_INDEX);
+ return generateString(length, LOWER_ALPHANUMERIC_INDEX);
}
/**
@@ -35,7 +35,7 @@ public final class RandomStringUtils {
* @return The random hexadecimal string
*/
public static String generateHex(int length) {
- return generate(length, HEX_MAX_INDEX);
+ return generateString(length, HEX_MAX_INDEX);
}
/**
@@ -46,16 +46,16 @@ public final class RandomStringUtils {
* @return The random string
*/
public static String generateLowerUpper(int length) {
- return generate(length, CHARS.length());
+ return generateString(length, CHARS.length);
}
- private static String generate(int length, int maxIndex) {
+ private static String generateString(int length, int maxIndex) {
if (length < 0) {
throw new IllegalArgumentException("Length must be positive but was " + length);
}
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
- sb.append(CHARS.charAt(RANDOM.nextInt(maxIndex)));
+ sb.append(CHARS[RANDOM.nextInt(maxIndex)]);
}
return sb.toString();
}
diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java
index a4fc733e..1f200c0f 100644
--- a/src/main/java/fr/xephi/authme/util/StringUtils.java
+++ b/src/main/java/fr/xephi/authme/util/StringUtils.java
@@ -42,7 +42,7 @@ public final class StringUtils {
*
* @return True if the string contains at least one of the items
*/
- public static boolean containsAny(String str, String... pieces) {
+ public static boolean containsAny(String str, Iterable pieces) {
if (str == null) {
return false;
}
@@ -76,4 +76,20 @@ public final class StringUtils {
public static String formatException(Throwable th) {
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
}
+
+ /**
+ * Check that the given needle is in the middle of the haystack, i.e. that the haystack
+ * contains the needle and that it is not at the very start or end.
+ *
+ * @param needle the needle to search for
+ * @param haystack the haystack to search in
+ *
+ * @return true if the needle is in the middle of the word, false otherwise
+ */
+ // Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like
+ // isInsideString("11", "2211") would unexpectedly return true...
+ public static boolean isInsideString(char needle, String haystack) {
+ int index = haystack.indexOf(needle);
+ return index > 0 && index < haystack.length() - 1;
+ }
}
diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java
index b3ec53c7..4d6983df 100644
--- a/src/main/java/fr/xephi/authme/util/Utils.java
+++ b/src/main/java/fr/xephi/authme/util/Utils.java
@@ -1,7 +1,10 @@
package fr.xephi.authme.util;
import fr.xephi.authme.ConsoleLogger;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import java.util.Collection;
import java.util.regex.Pattern;
/**
@@ -50,6 +53,32 @@ public final class Utils {
}
}
+ /**
+ * Sends a message to the given sender (null safe), and logs the message to the console.
+ * This method is aware that the command sender might be the console sender and avoids
+ * displaying the message twice in this case.
+ *
+ * @param sender the sender to inform
+ * @param message the message to log and send
+ */
+ public static void logAndSendMessage(CommandSender sender, String message) {
+ ConsoleLogger.info(message);
+ // Make sure sender is not console user, which will see the message from ConsoleLogger already
+ if (sender != null && !(sender instanceof ConsoleCommandSender)) {
+ sender.sendMessage(message);
+ }
+ }
+
+ /**
+ * Null-safe way to check whether a collection is empty or not.
+ *
+ * @param coll The collection to verify
+ * @return True if the collection is null or empty, false otherwise
+ */
+ public static boolean isCollectionEmpty(Collection> coll) {
+ return coll == null || coll.isEmpty();
+ }
+
/**
* Return the available core count of the JVM.
*
diff --git a/src/main/java/fr/xephi/authme/util/expiring/Duration.java b/src/main/java/fr/xephi/authme/util/expiring/Duration.java
new file mode 100644
index 00000000..ecc86299
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/expiring/Duration.java
@@ -0,0 +1,72 @@
+package fr.xephi.authme.util.expiring;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents a duration in time, defined by a time unit and a duration.
+ */
+public class Duration {
+
+ private final long duration;
+ private final TimeUnit unit;
+
+ /**
+ * Constructor.
+ *
+ * @param duration the duration
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public Duration(long duration, TimeUnit unit) {
+ this.duration = duration;
+ this.unit = unit;
+ }
+
+ /**
+ * Creates a Duration object for the given duration and unit in the most suitable time unit.
+ * For example, {@code createWithSuitableUnit(120, TimeUnit.SECONDS)} will return a Duration
+ * object of 2 minutes.
+ *
+ * This method only considers the time units days, hours, minutes, and seconds for the objects
+ * it creates. Conversion is done with {@link TimeUnit#convert} and so always rounds the
+ * results down.
+ *
+ * Further examples:
+ * createWithSuitableUnit(299, TimeUnit.MINUTES); // 4 hours
+ * createWithSuitableUnit(700, TimeUnit.MILLISECONDS); // 0 seconds
+ *
+ * @param sourceDuration the duration
+ * @param sourceUnit the time unit the duration is expressed in
+ * @return Duration object using the most suitable time unit
+ */
+ public static Duration createWithSuitableUnit(long sourceDuration, TimeUnit sourceUnit) {
+ long durationMillis = Math.abs(TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit));
+
+ TimeUnit targetUnit;
+ if (durationMillis > 1000L * 60L * 60L * 24L) {
+ targetUnit = TimeUnit.DAYS;
+ } else if (durationMillis > 1000L * 60L * 60L) {
+ targetUnit = TimeUnit.HOURS;
+ } else if (durationMillis > 1000L * 60L) {
+ targetUnit = TimeUnit.MINUTES;
+ } else {
+ targetUnit = TimeUnit.SECONDS;
+ }
+
+ long durationInTargetUnit = targetUnit.convert(sourceDuration, sourceUnit);
+ return new Duration(durationInTargetUnit, targetUnit);
+ }
+
+ /**
+ * @return the duration
+ */
+ public long getDuration() {
+ return duration;
+ }
+
+ /**
+ * @return the time unit in which the duration is expressed
+ */
+ public TimeUnit getTimeUnit() {
+ return unit;
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java
new file mode 100644
index 00000000..e920805c
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringMap.java
@@ -0,0 +1,131 @@
+package fr.xephi.authme.util.expiring;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Map with expiring entries. Following a configured amount of time after
+ * an entry has been inserted, the map will act as if the entry does not
+ * exist.
+ *
+ * Time starts counting directly after insertion. Inserting a new entry with
+ * a key that already has a value will "reset" the expiration. Although the
+ * expiration can be redefined later on, only entries which are inserted
+ * afterwards will use the new expiration.
+ *
+ * An expiration of {@code <= 0} will make the map expire all entries
+ * immediately after insertion. Note that the map does not remove expired
+ * entries automatically; this is only done when calling
+ * {@link #removeExpiredEntries()}.
+ *
+ * @param the key type
+ * @param the value type
+ */
+public class ExpiringMap {
+
+ protected final Map> entries = new ConcurrentHashMap<>();
+ private long expirationMillis;
+
+ /**
+ * Constructor.
+ *
+ * @param duration the duration of time after which entries expire
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public ExpiringMap(long duration, TimeUnit unit) {
+ setExpiration(duration, unit);
+ }
+
+ /**
+ * Returns the value associated with the given key,
+ * if available and not expired.
+ *
+ * @param key the key to look up
+ * @return the associated value, or {@code null} if not available
+ */
+ public V get(K key) {
+ ExpiringEntry value = entries.get(key);
+ if (value == null) {
+ return null;
+ } else if (System.currentTimeMillis() > value.getExpiration()) {
+ entries.remove(key);
+ return null;
+ }
+ return value.getValue();
+ }
+
+ /**
+ * Inserts a value for the given key. Overwrites a previous value
+ * for the key if it exists.
+ *
+ * @param key the key to insert a value for
+ * @param value the value to insert
+ */
+ public void put(K key, V value) {
+ long expiration = System.currentTimeMillis() + expirationMillis;
+ entries.put(key, new ExpiringEntry<>(value, expiration));
+ }
+
+ /**
+ * Removes the value for the given key, if available.
+ *
+ * @param key the key to remove the value for
+ */
+ public void remove(K key) {
+ entries.remove(key);
+ }
+
+ /**
+ * Removes all entries which have expired from the internal structure.
+ */
+ public void removeExpiredEntries() {
+ entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue().getExpiration());
+ }
+
+ /**
+ * Sets a new expiration duration. Note that already present entries
+ * will still make use of the old expiration.
+ *
+ * @param duration the duration of time after which entries expire
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public void setExpiration(long duration, TimeUnit unit) {
+ this.expirationMillis = unit.toMillis(duration);
+ }
+
+ /**
+ * Returns whether this map is empty. This reflects the state of the
+ * internal map, which may contain expired entries only. The result
+ * may change after running {@link #removeExpiredEntries()}.
+ *
+ * @return true if map is really empty, false otherwise
+ */
+ public boolean isEmpty() {
+ return entries.isEmpty();
+ }
+
+ /**
+ * Class holding a value paired with an expiration timestamp.
+ *
+ * @param the value type
+ */
+ protected static final class ExpiringEntry {
+
+ private final V value;
+ private final long expiration;
+
+ ExpiringEntry(V value, long expiration) {
+ this.value = value;
+ this.expiration = expiration;
+ }
+
+ V getValue() {
+ return value;
+ }
+
+ long getExpiration() {
+ return expiration;
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java
new file mode 100644
index 00000000..fea8fb31
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/expiring/ExpiringSet.java
@@ -0,0 +1,125 @@
+package fr.xephi.authme.util.expiring;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Set whose entries expire after a configurable amount of time. Once an entry
+ * has expired, the set will act as if the entry no longer exists. Time starts
+ * counting after the entry has been inserted.
+ *
+ * Internally, expired entries are not guaranteed to be cleared automatically.
+ * A cleanup of all expired entries may be triggered with
+ * {@link #removeExpiredEntries()}. Adding an entry that is already present
+ * effectively resets its expiration.
+ *
+ * @param the type of the entries
+ */
+public class ExpiringSet {
+
+ private Map entries = new ConcurrentHashMap<>();
+ private long expirationMillis;
+
+ /**
+ * Constructor.
+ *
+ * @param duration the duration of time after which entries expire
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public ExpiringSet(long duration, TimeUnit unit) {
+ setExpiration(duration, unit);
+ }
+
+ /**
+ * Adds an entry to the set.
+ *
+ * @param entry the entry to add
+ */
+ public void add(E entry) {
+ entries.put(entry, System.currentTimeMillis() + expirationMillis);
+ }
+
+ /**
+ * Returns whether this set contains the given entry, if it hasn't expired.
+ *
+ * @param entry the entry to check
+ * @return true if the entry is present and not expired, false otherwise
+ */
+ public boolean contains(E entry) {
+ Long expiration = entries.get(entry);
+ if (expiration == null) {
+ return false;
+ } else if (expiration > System.currentTimeMillis()) {
+ return true;
+ } else {
+ entries.remove(entry);
+ return false;
+ }
+ }
+
+ /**
+ * Removes the given entry from the set (if present).
+ *
+ * @param entry the entry to remove
+ */
+ public void remove(E entry) {
+ entries.remove(entry);
+ }
+
+ /**
+ * Removes all entries from the set.
+ */
+ public void clear() {
+ entries.clear();
+ }
+
+ /**
+ * Removes all entries which have expired from the internal structure.
+ */
+ public void removeExpiredEntries() {
+ entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue());
+ }
+
+ /**
+ * Returns the duration of the entry until it expires (provided it is not removed or re-added).
+ * If the entry does not exist, a duration of -1 seconds is returned.
+ *
+ * @param entry the entry whose duration before it expires should be returned
+ * @return duration the entry will remain in the set (if there are not modifications)
+ */
+ public Duration getExpiration(E entry) {
+ Long expiration = entries.get(entry);
+ if (expiration == null) {
+ return new Duration(-1, TimeUnit.SECONDS);
+ }
+ long stillPresentMillis = expiration - System.currentTimeMillis();
+ if (stillPresentMillis < 0) {
+ entries.remove(entry);
+ return new Duration(-1, TimeUnit.SECONDS);
+ }
+ return Duration.createWithSuitableUnit(stillPresentMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Sets a new expiration duration. Note that already present entries
+ * will still make use of the old expiration.
+ *
+ * @param duration the duration of time after which entries expire
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public void setExpiration(long duration, TimeUnit unit) {
+ this.expirationMillis = unit.toMillis(duration);
+ }
+
+ /**
+ * Returns whether this map is empty. This reflects the state of the
+ * internal map, which may contain expired entries only. The result
+ * may change after running {@link #removeExpiredEntries()}.
+ *
+ * @return true if map is really empty, false otherwise
+ */
+ public boolean isEmpty() {
+ return entries.isEmpty();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java
new file mode 100644
index 00000000..c3ae908c
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/expiring/TimedCounter.java
@@ -0,0 +1,50 @@
+package fr.xephi.authme.util.expiring;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Keeps a count per key which expires after a configurable amount of time.
+ *
+ * Once the expiration of an entry has been reached, the counter resets
+ * to 0. The counter returns 0 rather than {@code null} for any given key.
+ */
+public class TimedCounter extends ExpiringMap {
+
+ /**
+ * Constructor.
+ *
+ * @param duration the duration of time after which entries expire
+ * @param unit the time unit in which {@code duration} is expressed
+ */
+ public TimedCounter(long duration, TimeUnit unit) {
+ super(duration, unit);
+ }
+
+ @Override
+ public Integer get(K key) {
+ Integer value = super.get(key);
+ return value == null ? 0 : value;
+ }
+
+ /**
+ * Increments the value stored for the provided key.
+ *
+ * @param key the key to increment the counter for
+ */
+ public void increment(K key) {
+ put(key, get(key) + 1);
+ }
+
+ /**
+ * Calculates the total of all non-expired entries in this counter.
+ *
+ * @return the total of all valid entries
+ */
+ public int total() {
+ long currentTime = System.currentTimeMillis();
+ return entries.values().stream()
+ .filter(entry -> currentTime <= entry.getExpiration())
+ .map(ExpiringEntry::getValue)
+ .reduce(0, Integer::sum);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java b/src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java
new file mode 100644
index 00000000..5fb0cd3d
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/DependentTag.java
@@ -0,0 +1,35 @@
+package fr.xephi.authme.util.lazytags;
+
+import java.util.function.Function;
+
+/**
+ * Replaceable tag whose value depends on an argument.
+ *
+ * @param the argument type
+ */
+public class DependentTag implements Tag {
+
+ private final String name;
+ private final Function replacementFunction;
+
+ /**
+ * Constructor.
+ *
+ * @param name the tag (placeholder) that will be replaced
+ * @param replacementFunction the function producing the replacement
+ */
+ public DependentTag(String name, Function replacementFunction) {
+ this.name = name;
+ this.replacementFunction = replacementFunction;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue(A argument) {
+ return replacementFunction.apply(argument);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java
new file mode 100644
index 00000000..a5bb58a2
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/SimpleTag.java
@@ -0,0 +1,29 @@
+package fr.xephi.authme.util.lazytags;
+
+import java.util.function.Supplier;
+
+/**
+ * Tag to be replaced that does not depend on an argument.
+ *
+ * @param type of the argument (not used in this implementation)
+ */
+public class SimpleTag implements Tag {
+
+ private final String name;
+ private final Supplier replacementFunction;
+
+ public SimpleTag(String name, Supplier replacementFunction) {
+ this.name = name;
+ this.replacementFunction = replacementFunction;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue(A argument) {
+ return replacementFunction.get();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java
new file mode 100644
index 00000000..2c7c6ba5
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java
@@ -0,0 +1,22 @@
+package fr.xephi.authme.util.lazytags;
+
+/**
+ * Represents a tag in a text to be replaced with a value (which may depend on some argument).
+ *
+ * @param argument type the replacement may depend on
+ */
+public interface Tag {
+
+ /**
+ * @return the tag to replace
+ */
+ String getName();
+
+ /**
+ * Returns the value to replace the tag with for the given argument.
+ *
+ * @param argument the argument to evaluate the replacement for
+ * @return the replacement
+ */
+ String getValue(A argument);
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java
new file mode 100644
index 00000000..677b30e2
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/TagBuilder.java
@@ -0,0 +1,21 @@
+package fr.xephi.authme.util.lazytags;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Utility class for creating tags.
+ */
+public final class TagBuilder {
+
+ private TagBuilder() {
+ }
+
+ public static Tag createTag(String name, Function replacementFunction) {
+ return new DependentTag<>(name, replacementFunction);
+ }
+
+ public static Tag createTag(String name, Supplier replacementFunction) {
+ return new SimpleTag<>(name, replacementFunction);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java
new file mode 100644
index 00000000..a9d19319
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/TagReplacer.java
@@ -0,0 +1,102 @@
+package fr.xephi.authme.util.lazytags;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Replaces tags lazily by first determining which tags are being used
+ * and only applying those replacements afterwards.
+ *
+ * @param the argument type
+ */
+public final class TagReplacer {
+
+ private final List> tags;
+ private final Collection messages;
+
+ /**
+ * Private constructor. Use {@link #newReplacer(Collection, Collection)}.
+ *
+ * @param tags the tags that are being used in the messages
+ * @param messages the messages
+ */
+ private TagReplacer(List> tags, Collection messages) {
+ this.tags = tags;
+ this.messages = messages;
+ }
+
+ /**
+ * Creates a new instance of this class, which will provide the given
+ * messages adapted with the provided tags.
+ *
+ * @param allTags all available tags
+ * @param messages the messages to use
+ * @param the argument type
+ * @return new tag replacer instance
+ */
+ public static TagReplacer newReplacer(Collection> allTags, Collection messages) {
+ List> usedTags = determineUsedTags(allTags, messages);
+ return new TagReplacer<>(usedTags, messages);
+ }
+
+ /**
+ * Returns the messages with the tags applied for the given argument.
+ *
+ * @param argument the argument to get the messages for
+ * @return the adapted messages
+ */
+ public List getAdaptedMessages(A argument) {
+ // Note ljacqu 20170121: Using a Map might seem more natural here but we avoid doing so for performance
+ // Although the performance gain here is probably minimal...
+ List tagValues = new LinkedList<>();
+ for (Tag tag : tags) {
+ tagValues.add(new TagValue(tag.getName(), tag.getValue(argument)));
+ }
+
+ List adaptedMessages = new LinkedList<>();
+ for (String line : messages) {
+ String adaptedLine = line;
+ for (TagValue tagValue : tagValues) {
+ adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value);
+ }
+ adaptedMessages.add(adaptedLine);
+ }
+ return adaptedMessages;
+ }
+
+ /**
+ * Determines which tags are used somewhere in the given list of messages.
+ *
+ * @param allTags all available tags
+ * @param messages the messages
+ * @param argument type
+ * @return tags used at least once
+ */
+ private static List> determineUsedTags(Collection> allTags, Collection messages) {
+ return allTags.stream()
+ .filter(tag -> messages.stream().anyMatch(msg -> msg.contains(tag.getName())))
+ .collect(Collectors.toList());
+ }
+
+ /** (Tag, value) pair. */
+ private static final class TagValue {
+
+ /** The tag to replace. */
+ private final String tag;
+ /** The value to replace with. */
+ private final String value;
+
+ TagValue(String tag, String value) {
+ this.tag = tag;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return "TagValue[tag='" + tag + "', value='" + value + "']";
+ }
+ }
+
+}
diff --git a/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java
new file mode 100644
index 00000000..ce9487e2
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/util/lazytags/WrappedTagReplacer.java
@@ -0,0 +1,61 @@
+package fr.xephi.authme.util.lazytags;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Applies tags lazily to the String property of an item. This class wraps
+ * a {@link TagReplacer} with the extraction of the String property and
+ * the creation of new items with the adapted string property.
+ *
+ * @param the item type
+ * @param the argument type to evaluate the replacements
+ */
+public class WrappedTagReplacer {
+
+ private final Collection items;
+ private final BiFunction itemCreator;
+ private final TagReplacer tagReplacer;
+
+ /**
+ * Constructor.
+ *
+ * @param allTags all available tags
+ * @param items the items to apply the replacements on
+ * @param stringGetter getter of the String property to adapt on the items
+ * @param itemCreator a function taking (T, String): the original item and the adapted String, returning a new item
+ */
+ public WrappedTagReplacer(Collection> allTags,
+ Collection items,
+ Function super T, String> stringGetter,
+ BiFunction itemCreator) {
+ this.items = items;
+ this.itemCreator = itemCreator;
+
+ List stringItems = items.stream().map(stringGetter).collect(Collectors.toList());
+ tagReplacer = TagReplacer.newReplacer(allTags, stringItems);
+ }
+
+ /**
+ * Creates adapted items for the given argument.
+ *
+ * @param argument the argument to adapt the items for
+ * @return the adapted items
+ */
+ public List getAdaptedItems(A argument) {
+ List adaptedStrings = tagReplacer.getAdaptedMessages(argument);
+ List adaptedItems = new LinkedList<>();
+
+ Iterator originalItemsIter = items.iterator();
+ Iterator