Merge pull request #87 from AuthMe-Team/347-config-rewrite

347 config rewrite
This commit is contained in:
ljacqu 2016-01-17 13:01:28 +01:00
commit 737d7f1ca0
49 changed files with 2908 additions and 200 deletions

View File

@ -1,34 +1,9 @@
package fr.xephi.authme;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.mcstats.Metrics;
import org.mcstats.Metrics.Graph;
import com.earth2me.essentials.Essentials;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import com.onarandombox.MultiverseCore.MultiverseCore;
import fr.xephi.authme.api.API;
import fr.xephi.authme.api.NewAPI;
import fr.xephi.authme.cache.auth.PlayerAuth;
@ -73,11 +48,36 @@ import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.OtherAccounts;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.Spawn;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.util.GeoLiteAPI;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import fr.xephi.authme.util.Wrapper;
import net.minelink.ctplus.CombatTagPlus;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.mcstats.Metrics;
import org.mcstats.Metrics.Graph;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* The AuthMe main class.
@ -99,6 +99,7 @@ public class AuthMe extends JavaPlugin {
private CommandHandler commandHandler = null;
private PermissionsManager permsMan = null;
private Settings settings;
private NewSetting newSettings;
private Messages messages;
private JsonCache playerBackup;
private ModuleManager moduleManager;
@ -215,6 +216,7 @@ public class AuthMe extends JavaPlugin {
setEnabled(false);
return;
}
newSettings = createNewSetting();
// Set up messages & password security
messages = Messages.getInstance();
@ -235,7 +237,7 @@ public class AuthMe extends JavaPlugin {
// Set up the permissions manager and command handler
permsMan = initializePermissionsManager();
commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity);
commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity, newSettings);
// Set up the module manager
setupModuleManager();
@ -417,11 +419,12 @@ public class AuthMe extends JavaPlugin {
}
private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages,
PasswordSecurity passwordSecurity) {
PasswordSecurity passwordSecurity, NewSetting settings) {
HelpProvider helpProvider = new HelpProvider(permissionsManager);
Set<CommandDescription> baseCommands = CommandInitializer.buildCommands();
CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider);
CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity);
CommandMapper mapper = new CommandMapper(baseCommands, permissionsManager);
CommandService commandService = new CommandService(
this, mapper, helpProvider, messages, passwordSecurity, permissionsManager, settings);
return new CommandHandler(commandService);
}
@ -443,19 +446,24 @@ public class AuthMe extends JavaPlugin {
* @return True on success, false on failure.
*/
private boolean loadSettings() {
// TODO: new configuration style (more files)
try {
settings = new Settings(this);
Settings.reload();
} catch (Exception e) {
ConsoleLogger.writeStackTrace(e);
ConsoleLogger.showError("Can't load the configuration file... Something went wrong, to avoid security issues the server will shutdown!");
ConsoleLogger.showError("Can't load the configuration file... Something went wrong. "
+ "To avoid security issues the server will shut down!");
server.shutdown();
return true;
}
return false;
}
private NewSetting createNewSetting() {
File configFile = new File(getDataFolder() + "config.yml");
return new NewSetting(getConfig(), configFile);
}
/**
* Set up the console filter.
*/

View File

@ -3,6 +3,9 @@ package fr.xephi.authme.command;
import java.util.ArrayList;
import java.util.List;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import fr.xephi.authme.util.StringUtils;
@ -13,6 +16,12 @@ import fr.xephi.authme.util.StringUtils;
*/
public class CommandHandler {
/**
* The threshold for suggesting a similar command. If the difference is below this value, we will
* ask the player whether he meant the similar command.
*/
private static final double SUGGEST_COMMAND_THRESHOLD = 0.75;
private final CommandService commandService;
/**
@ -40,14 +49,32 @@ public class CommandHandler {
parts.add(0, bukkitCommandLabel);
FoundCommandResult result = commandService.mapPartsToCommand(sender, parts);
if (FoundResultStatus.SUCCESS.equals(result.getResultStatus())) {
executeCommand(sender, result);
} else {
commandService.outputMappingError(sender, result);
}
handleCommandResult(sender, result);
return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus());
}
private void handleCommandResult(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
executeCommand(sender, result);
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
break;
case INCORRECT_ARGUMENTS:
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
sendUnknownCommandMessage(sender, result);
break;
case NO_PERMISSION:
sendPermissionDeniedError(sender);
break;
default:
throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'");
}
}
/**
* Execute the command for the given command sender.
*
@ -76,6 +103,46 @@ public class CommandHandler {
return cleanArguments;
}
/**
* Show an "unknown command" message to the user and suggest an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) {
sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD
+ CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?");
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!commandService.getPermissionsManager().hasPermission(sender, command)) {
sendPermissionDeniedError(sender);
return;
}
// Show the command argument help
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
commandService.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
List<String> labels = result.getLabels();
String childLabel = labels.size() >= 2 ? labels.get(1) : "";
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE
+ "/" + labels.get(0) + " help " + childLabel);
}
// TODO ljacqu 20151212: Remove me once I am a MessageKey
private static void sendPermissionDeniedError(CommandSender sender) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!");
}
}

View File

@ -1,13 +1,11 @@
package fr.xephi.authme.command;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
@ -19,101 +17,24 @@ import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
/**
* The AuthMe command handler, responsible for mapping incoming command parts to the correct {@link CommandDescription}
* or to display help messages for erroneous invocations (unknown command, no permission, etc.).
* The AuthMe command handler, responsible for mapping incoming
* command parts to the correct {@link CommandDescription}.
*/
public class CommandMapper {
/**
* The threshold for suggesting a similar command. If the difference is below this value, we will
* ask the player whether he meant the similar command.
*/
private static final double SUGGEST_COMMAND_THRESHOLD = 0.75;
/**
* The class of the help command, to which the base label should also be passed in the arguments.
*/
private static final Class<? extends ExecutableCommand> HELP_COMMAND_CLASS = HelpCommand.class;
private final Set<CommandDescription> baseCommands;
private final Messages messages;
private final PermissionsManager permissionsManager;
private final HelpProvider helpProvider;
public CommandMapper(Set<CommandDescription> baseCommands, Messages messages,
PermissionsManager permissionsManager, HelpProvider helpProvider) {
public CommandMapper(Set<CommandDescription> baseCommands, PermissionsManager permissionsManager) {
this.baseCommands = baseCommands;
this.messages = messages;
this.permissionsManager = permissionsManager;
this.helpProvider = helpProvider;
}
public void outputStandardError(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
// Successful mapping, so no error to output
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
break;
case INCORRECT_ARGUMENTS:
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
sendUnknownCommandMessage(sender, result);
break;
case NO_PERMISSION:
sendPermissionDeniedError(sender);
break;
default:
throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'");
}
}
/**
* Show an "unknown command" message to the user and suggest an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) {
sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD
+ CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?");
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!permissionsManager.hasPermission(sender, command)) {
sendPermissionDeniedError(sender);
return;
}
// Show the command argument help
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
List<String> lines = helpProvider.printHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
for (String line : lines) {
sender.sendMessage(line);
}
List<String> labels = result.getLabels();
String childLabel = labels.size() >= 2 ? labels.get(1) : "";
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE
+ "/" + labels.get(0) + " help " + childLabel);
}
// TODO ljacqu 20151212: Remove me once I am a MessageKey
private static void sendPermissionDeniedError(CommandSender sender) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!");
}
/**
* Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments.

View File

@ -1,9 +1,5 @@
package fr.xephi.authme.command;
import java.util.List;
import org.bukkit.command.CommandSender;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.datasource.DataSource;
@ -12,6 +8,11 @@ import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.command.CommandSender;
import java.util.List;
/**
* Service for implementations of {@link ExecutableCommand} to execute some common tasks.
@ -24,6 +25,8 @@ public class CommandService {
private final HelpProvider helpProvider;
private final CommandMapper commandMapper;
private final PasswordSecurity passwordSecurity;
private final PermissionsManager permissionsManager;
private final NewSetting settings;
/**
* Constructor.
@ -33,14 +36,19 @@ public class CommandService {
* @param helpProvider Help provider
* @param messages Messages instance
* @param passwordSecurity The Password Security instance
* @param permissionsManager The permissions manager
* @param settings The settings manager
*/
public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages,
PasswordSecurity passwordSecurity) {
PasswordSecurity passwordSecurity, PermissionsManager permissionsManager,
NewSetting settings) {
this.authMe = authMe;
this.messages = messages;
this.helpProvider = helpProvider;
this.commandMapper = commandMapper;
this.passwordSecurity = passwordSecurity;
this.permissionsManager = permissionsManager;
this.settings = settings;
}
/**
@ -75,17 +83,6 @@ public class CommandService {
return commandMapper.mapPartsToCommand(sender, commandParts);
}
/**
* Output the standard error message for the status in the provided {@link FoundCommandResult} object.
* Does not output anything for successful mappings.
*
* @param sender The sender to output the error to
* @param result The mapping result to process
*/
public void outputMappingError(CommandSender sender, FoundCommandResult result) {
commandMapper.outputStandardError(sender, result);
}
/**
* Run the given task asynchronously with the Bukkit scheduler.
*
@ -142,8 +139,7 @@ public class CommandService {
* @return the permissions manager
*/
public PermissionsManager getPermissionsManager() {
// TODO ljacqu 20151226: Might be nicer to pass the perm manager via constructor
return authMe.getPermissionsManager();
return permissionsManager;
}
/**
@ -156,4 +152,15 @@ public class CommandService {
return messages.retrieve(key);
}
/**
* Retrieve the given property's value.
*
* @param property The property to retrieve
* @param <T> The type of the property
* @return The property's value
*/
public <T> T getProperty(Property<T> property) {
return settings.getProperty(property);
}
}

View File

@ -8,7 +8,8 @@ import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import org.bukkit.command.CommandSender;
import java.util.List;
@ -27,7 +28,7 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
// Validate the password
String playerPassLowerCase = playerPass.toLowerCase();
if (!playerPassLowerCase.matches(Settings.getPassRegex)) {
if (!playerPassLowerCase.matches(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX))) {
commandService.send(sender, MessageKey.PASSWORD_MATCH_ERROR);
return;
}
@ -35,12 +36,12 @@ public class ChangePasswordAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return;
}
if (playerPassLowerCase.length() < Settings.getPasswordMinLen
|| playerPassLowerCase.length() > Settings.passwordMaxLength) {
if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH);
return;
}
if (!Settings.unsafePasswords.isEmpty() && Settings.unsafePasswords.contains(playerPassLowerCase)) {
if (commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(playerPassLowerCase)) {
commandService.send(sender, MessageKey.PASSWORD_UNSAFE_ERROR);
return;
}

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -35,8 +36,8 @@ public class RegisterAdminCommand implements ExecutableCommand {
commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return;
}
if (playerPassLowerCase.length() < Settings.getPasswordMinLen
|| playerPassLowerCase.length() > Settings.passwordMaxLength) {
if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH);
return;
}

View File

@ -6,7 +6,7 @@ import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.security.RandomString;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.util.Wrapper;
import org.bukkit.entity.Player;
@ -28,20 +28,20 @@ public class CaptchaCommand extends PlayerCommand {
return;
}
if (!Settings.useCaptcha) {
if (!commandService.getProperty(SecuritySettings.USE_CAPTCHA)) {
commandService.send(player, MessageKey.USAGE_LOGIN);
return;
}
if (!plugin.cap.containsKey(playerNameLowerCase)) {
commandService.send(player, MessageKey.USAGE_LOGIN);
return;
}
if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) {
if (!captcha.equals(plugin.cap.get(playerNameLowerCase))) {
plugin.cap.remove(playerNameLowerCase);
String randStr = RandomString.generate(Settings.captchaLength);
int captchaLength = commandService.getProperty(SecuritySettings.CAPTCHA_LENGTH);
String randStr = RandomString.generate(captchaLength);
plugin.cap.put(playerNameLowerCase, randStr);
commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase));
return;

View File

@ -5,7 +5,8 @@ import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.task.ChangePasswordTask;
import fr.xephi.authme.util.Wrapper;
import org.bukkit.entity.Player;
@ -32,7 +33,7 @@ public class ChangePasswordCommand extends PlayerCommand {
// Make sure the password is allowed
String playerPassLowerCase = newPassword.toLowerCase();
if (!playerPassLowerCase.matches(Settings.getPassRegex)) {
if (!playerPassLowerCase.matches(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX))) {
commandService.send(player, MessageKey.PASSWORD_MATCH_ERROR);
return;
}
@ -40,17 +41,18 @@ public class ChangePasswordCommand extends PlayerCommand {
commandService.send(player, MessageKey.PASSWORD_IS_USERNAME_ERROR);
return;
}
if (playerPassLowerCase.length() < Settings.getPasswordMinLen
|| playerPassLowerCase.length() > Settings.passwordMaxLength) {
if (playerPassLowerCase.length() < commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)
|| playerPassLowerCase.length() > commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)) {
commandService.send(player, MessageKey.INVALID_PASSWORD_LENGTH);
return;
}
if (!Settings.unsafePasswords.isEmpty() && Settings.unsafePasswords.contains(playerPassLowerCase)) {
if (commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS).contains(playerPassLowerCase)) {
commandService.send(player, MessageKey.PASSWORD_UNSAFE_ERROR);
return;
}
AuthMe plugin = AuthMe.getInstance();
// TODO ljacqu 20160117: Call async task via Management
commandService.runTaskAsynchronously(new ChangePasswordTask(plugin, player, oldPassword, newPassword));
}
}

View File

@ -43,6 +43,6 @@ public class RegisterCommand extends PlayerCommand {
@Override
public String getAlternativeCommand() {
return "authme register <playername> <password>";
return "/authme register <playername> <password>";
}
}

View File

@ -55,7 +55,7 @@ public class PasswordSecurity {
* @param hashedPassword The encrypted password to test the clear-text password against
* @param playerName The name of the player
*
* @return True if the
* @return True if there was a password match with another encryption method, false otherwise
*/
private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword,
String playerName) {

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Wrapper;
import org.bukkit.configuration.file.YamlConfiguration;
@ -311,7 +312,7 @@ public final class Settings {
try {
return Files.toString(EMAIL_FILE, Charsets.UTF_8);
} catch (IOException e) {
ConsoleLogger.showError(e.getMessage());
ConsoleLogger.showError("Error loading email text: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
return "";
}

View File

@ -0,0 +1,29 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class BackupSettings implements SettingsClass {
@Comment("Enable or disable automatic backup")
public static final Property<Boolean> ENABLED =
newProperty("BackupSystem.ActivateBackup", false);
@Comment("Set backup at every start of server")
public static final Property<Boolean> ON_SERVER_START =
newProperty("BackupSystem.OnServerStart", false);
@Comment("Set backup at every stop of server")
public static final Property<Boolean> ON_SERVER_STOP =
newProperty("BackupSystem.OnServerStop", true);
@Comment(" Windows only mysql installation Path")
public static final Property<String> MYSQL_WINDOWS_PATH =
newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\");
private BackupSettings() {
}
}

View File

@ -0,0 +1,32 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
public class ConverterSettings implements SettingsClass {
@Comment("Rakamak file name")
public static final Property<String> RAKAMAK_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.fileName", "users.rak");
@Comment("Rakamak use IP?")
public static final Property<Boolean> RAKAMAK_USE_IP =
newProperty(BOOLEAN, "Converter.Rakamak.useIP", false);
@Comment("Rakamak IP file name")
public static final Property<String> RAKAMAK_IP_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.ipFileName", "UsersIp.rak");
@Comment("CrazyLogin database file name")
public static final Property<String> CRAZYLOGIN_FILE_NAME =
newProperty(STRING, "Converter.CrazyLogin.fileName", "accounts.db");
private ConverterSettings() {
}
}

View File

@ -0,0 +1,108 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class DatabaseSettings implements SettingsClass {
@Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"})
public static final Property<DataSource.DataSourceType> BACKEND =
newProperty(DataSource.DataSourceType.class, "DataSource.backend", DataSource.DataSourceType.SQLITE);
@Comment("Enable database caching, should improve database performance")
public static final Property<Boolean> USE_CACHING =
newProperty("DataSource.caching", true);
@Comment("Database host address")
public static final Property<String> MYSQL_HOST =
newProperty("DataSource.mySQLHost", "127.0.0.1");
@Comment("Database port")
public static final Property<String> MYSQL_PORT =
newProperty("DataSource.mySQLPort", "3306");
@Comment("Username about Database Connection Infos")
public static final Property<String> MYSQL_USERNAME =
newProperty("DataSource.mySQLUsername", "authme");
@Comment("Password about Database Connection Infos")
public static final Property<String> MYSQL_PASSWORD =
newProperty("DataSource.mySQLPassword", "123456");
@Comment("Database Name, use with converters or as SQLITE database name")
public static final Property<String> MYSQL_DATABASE =
newProperty("DataSource.mySQLDatabase", "authme");
@Comment("Table of the database")
public static final Property<String> MYSQL_TABLE =
newProperty("DataSource.mySQLTablename", "authme");
@Comment("Column of IDs to sort data")
public static final Property<String> MYSQL_COL_ID =
newProperty("DataSource.mySQLColumnId", "id");
@Comment("Column for storing or checking players nickname")
public static final Property<String> MYSQL_COL_NAME =
newProperty("DataSource.mySQLColumnName", "username");
@Comment("Column for storing or checking players RealName ")
public static final Property<String> MYSQL_COL_REALNAME =
newProperty("DataSource.mySQLRealName", "realname");
@Comment("Column for storing players passwords")
public static final Property<String> MYSQL_COL_PASSWORD =
newProperty("DataSource.mySQLColumnPassword", "password");
@Comment("Column for storing players passwords salts")
public static final Property<String> MYSQL_COL_SALT =
newProperty("ExternalBoardOptions.mySQLColumnSalt", "");
@Comment("Column for storing players emails")
public static final Property<String> MYSQL_COL_EMAIL =
newProperty("DataSource.mySQLColumnEmail", "email");
@Comment("Column for storing if a player is logged in or not")
public static final Property<String> MYSQL_COL_ISLOGGED =
newProperty("DataSource.mySQLColumnLogged", "isLogged");
@Comment("Column for storing players ips")
public static final Property<String> MYSQL_COL_IP =
newProperty("DataSource.mySQLColumnIp", "ip");
@Comment("Column for storing players lastlogins")
public static final Property<String> MYSQL_COL_LASTLOGIN =
newProperty("DataSource.mySQLColumnLastLogin", "lastlogin");
@Comment("Column for storing player LastLocation - X")
public static final Property<String> MYSQL_COL_LASTLOC_X =
newProperty("DataSource.mySQLlastlocX", "x");
@Comment("Column for storing player LastLocation - Y")
public static final Property<String> MYSQL_COL_LASTLOC_Y =
newProperty("DataSource.mySQLlastlocY", "y");
@Comment("Column for storing player LastLocation - Z")
public static final Property<String> MYSQL_COL_LASTLOC_Z =
newProperty("DataSource.mySQLlastlocZ", "z");
@Comment("Column for storing player LastLocation - World Name")
public static final Property<String> MYSQL_COL_LASTLOC_WORLD =
newProperty("DataSource.mySQLlastlocWorld", "world");
@Comment("Column for storing players groups")
public static final Property<String> MYSQL_COL_GROUP =
newProperty("ExternalBoardOptions.mySQLColumnGroup", "");
@Comment("Enable this when you allow registration through a website")
public static final Property<Boolean> MYSQL_WEBSITE =
newProperty("DataSource.mySQLWebsite", false);
private DatabaseSettings() {
}
}

View File

@ -0,0 +1,76 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class EmailSettings implements SettingsClass {
@Comment("Email SMTP server host")
public static final Property<String> SMTP_HOST =
newProperty(STRING, "Email.mailSMTP", "smtp.gmail.com");
@Comment("Email SMTP server port")
public static final Property<Integer> SMTP_PORT =
newProperty(INTEGER, "Email.mailPort", 465);
@Comment("Email account which sends the mails")
public static final Property<String> MAIL_ACCOUNT =
newProperty(STRING, "Email.mailAccount", "");
@Comment("Email account password")
public static final Property<String> MAIL_PASSWORD =
newProperty(STRING, "Email.mailPassword", "");
@Comment("Custom sender name, replacing the mailAccount name in the email")
public static final Property<String> MAIL_SENDER_NAME =
newProperty("Email.mailSenderName", "");
@Comment("Recovery password length")
public static final Property<Integer> RECOVERY_PASSWORD_LENGTH =
newProperty(INTEGER, "Email.RecoveryPasswordLength", 8);
@Comment("Mail Subject")
public static final Property<String> RECOVERY_MAIL_SUBJECT =
newProperty(STRING, "Email.mailSubject", "Your new AuthMe password");
@Comment("Like maxRegPerIP but with email")
public static final Property<Integer> MAX_REG_PER_EMAIL =
newProperty(INTEGER, "Email.maxRegPerEmail", 1);
@Comment("Recall players to add an email?")
public static final Property<Boolean> RECALL_PLAYERS =
newProperty(BOOLEAN, "Email.recallPlayers", false);
@Comment("Delay in minute for the recall scheduler")
public static final Property<Integer> DELAY_RECALL =
newProperty(INTEGER, "Email.delayRecall", 5);
@Comment("Blacklist these domains for emails")
public static final Property<List<String>> DOMAIN_BLACKLIST =
newProperty(STRING_LIST, "Email.emailBlacklisted", "10minutemail.com");
@Comment("Whitelist ONLY these domains for emails")
public static final Property<List<String>> DOMAIN_WHITELIST =
newProperty(STRING_LIST, "Email.emailWhitelisted");
@Comment("Send the new password drawn in an image?")
public static final Property<Boolean> PASSWORD_AS_IMAGE =
newProperty(BOOLEAN, "Email.generateImage", false);
@Comment("The OAuth2 token")
public static final Property<String> OAUTH2_TOKEN =
newProperty(STRING, "Email.emailOauth2Token", "");
private EmailSettings() {
}
}

View File

@ -0,0 +1,72 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class HooksSettings implements SettingsClass {
@Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property<Boolean> MULTIVERSE =
newProperty("Hooks.multiverse", true);
@Comment("Do we need to hook with BungeeCord?")
public static final Property<Boolean> BUNGEECORD =
newProperty("Hooks.bungeecord", false);
@Comment("Send player to this BungeeCord server after register/login")
public static final Property<String> BUNGEECORD_SERVER =
newProperty("Hooks.sendPlayerTo", "");
@Comment("Do we need to disable Essentials SocialSpy on join?")
public static final Property<Boolean> DISABLE_SOCIAL_SPY =
newProperty("Hooks.disableSocialSpy", false);
@Comment("Do we need to force /motd Essentials command on join?")
public static final Property<Boolean> USE_ESSENTIALS_MOTD =
newProperty("Hooks.useEssentialsMotd", false);
@Comment("Do we need to cache custom Attributes?")
public static final Property<Boolean> CACHE_CUSTOM_ATTRIBUTES =
newProperty("Hooks.customAttributes", false);
@Comment("These features are only available on VeryGames Server Provider")
public static final Property<Boolean> ENABLE_VERYGAMES_IP_CHECK =
newProperty("VeryGames.enableIpCheck", false);
@Comment({
"-1 means disabled. If you want that only activated players",
"can log into your server, you can set here the group number",
"of unactivated users, needed for some forum/CMS support"})
public static final Property<Integer> NON_ACTIVATED_USERS_GROUP =
newProperty("ExternalBoardOptions.nonActivedUserGroup", -1);
@Comment("Other MySQL columns where we need to put the username (case-sensitive)")
public static final Property<List<String>> MYSQL_OTHER_USERNAME_COLS =
newProperty(PropertyType.STRING_LIST, "ExternalBoardOptions.mySQLOtherUsernameColumns");
@Comment("How much log2 rounds needed in BCrypt (do not change if you do not know what it does)")
public static final Property<Integer> BCRYPT_LOG2_ROUND =
newProperty("ExternalBoardOptions.bCryptLog2Round", 10);
@Comment("phpBB table prefix defined during the phpBB installation process")
public static final Property<String> PHPBB_TABLE_PREFIX =
newProperty("ExternalBoardOptions.phpbbTablePrefix", "phpbb_");
@Comment("phpBB activated group ID; 2 is the default registered group defined by phpBB")
public static final Property<Integer> PHPBB_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2);
@Comment("Wordpress prefix defined during WordPress installation")
public static final Property<String> WORDPRESS_TABLE_PREFIX =
newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_");
private HooksSettings() {
}
}

View File

@ -0,0 +1,156 @@
package fr.xephi.authme.settings.custom;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.FileConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* The new settings manager.
*/
public class NewSetting {
private File file;
private FileConfiguration configuration;
/**
* Constructor.
* Loads the file as YAML and checks its integrity.
*
* @param configuration The configuration to interact with
* @param file The configuration file
*/
public NewSetting(FileConfiguration configuration, File file) {
this.configuration = configuration;
this.file = file;
// TODO ljacqu 20160109: Ensure that save() works as desired (i.e. that it always produces valid YAML)
// and then uncomment the lines below. Once this is uncommented, the checks in the old Settings.java should
// be removed as we should check to rewrite the config.yml file only at one place
// --------
// PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
// if (!containsAllSettings(propertyMap)) {
// save(propertyMap);
// }
}
/**
* Constructor for testing purposes, allowing more options.
*
* @param configuration The FileConfiguration object to use
* @param file The file to write to
* @param propertyMap The property map whose properties should be verified for presence, or null to skip this
*/
@VisibleForTesting
NewSetting(FileConfiguration configuration, File file, PropertyMap propertyMap) {
this.configuration = configuration;
this.file = file;
if (propertyMap != null && !containsAllSettings(propertyMap)) {
save(propertyMap);
}
}
/**
* Get the given property from the configuration.
*
* @param property The property to retrieve
* @param <T> The property's type
* @return The property's value
*/
public <T> T getProperty(Property<T> property) {
return property.getFromFile(configuration);
}
public void save() {
save(SettingsFieldRetriever.getAllPropertyFields());
}
public void save(PropertyMap propertyMap) {
try (FileWriter writer = new FileWriter(file)) {
writer.write("");
// Contains all but the last node of the setting, e.g. [DataSource, mysql] for "DataSource.mysql.username"
List<String> currentPath = new ArrayList<>();
for (Map.Entry<Property<?>, String[]> entry : propertyMap.entrySet()) {
Property<?> property = entry.getKey();
// Handle properties
List<String> propertyPath = Arrays.asList(property.getPath().split("\\."));
List<String> commonPathParts = CollectionUtils.filterCommonStart(
currentPath, propertyPath.subList(0, propertyPath.size() - 1));
List<String> newPathParts = CollectionUtils.getRange(propertyPath, commonPathParts.size());
if (commonPathParts.isEmpty()) {
writer.append("\n");
}
int indentationLevel = commonPathParts.size();
if (newPathParts.size() > 1) {
for (String path : newPathParts.subList(0, newPathParts.size() - 1)) {
writer.append("\n")
.append(indent(indentationLevel))
.append(path)
.append(": ");
++indentationLevel;
}
}
for (String comment : entry.getValue()) {
writer.append("\n")
.append(indent(indentationLevel))
.append("# ")
.append(comment);
}
writer.append("\n")
.append(indent(indentationLevel))
.append(CollectionUtils.getRange(newPathParts, newPathParts.size() - 1).get(0))
.append(": ");
List<String> yamlLines = property.formatValueAsYaml(configuration);
String delim = "";
for (String yamlLine : yamlLines) {
writer.append(delim).append(yamlLine);
delim = "\n" + indent(indentationLevel);
}
currentPath = propertyPath.subList(0, propertyPath.size() - 1);
}
writer.flush();
writer.close();
} catch (IOException e) {
ConsoleLogger.showError("Could not save config file - " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
}
@VisibleForTesting
boolean containsAllSettings(PropertyMap propertyMap) {
for (Property<?> property : propertyMap.keySet()) {
if (!property.isPresent(configuration)) {
return false;
}
}
return true;
}
private static String indent(int level) {
// YAML uses indentation of 4 spaces
StringBuilder sb = new StringBuilder(level * 4);
for (int i = 0; i < level; ++i) {
sb.append(" ");
}
return sb.toString();
}
}

View File

@ -0,0 +1,60 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class PluginSettings implements SettingsClass {
@Comment("The name shown in the help messages")
public static final Property<String> HELP_HEADER =
newProperty("settings.helpHeader", "AuthMeReloaded");
@Comment({
"Do you want to enable the session feature?",
"If enabled, when a player authenticates successfully,",
"his IP and his nickname is saved.",
"The next time the player joins the server, if his IP",
"is the same as last time and the timeout hasn't",
"expired, he will not need to authenticate."
})
public static final Property<Boolean> SESSIONS_ENABLED =
newProperty("settings.sessions.enabled", false);
@Comment({
"After how many minutes should a session expire?",
"0 for unlimited time (Very dangerous, use it at your own risk!)",
"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"
})
public static final Property<Integer> 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<Boolean> SESSIONS_EXPIRE_ON_IP_CHANGE =
newProperty("settings.sessions.sessionExpireOnIpChange", true);
@Comment("Message language, available: en, de, br, cz, pl, fr, ru, hu, sk, es, zhtw, fi, zhcn, lt, it, ko, pt")
public static final Property<String> MESSAGES_LANGUAGE =
newProperty("settings.messagesLanguage", "en");
@Comment({
"Take care with this option; if you don't want",
"to use Vault and group switching of AuthMe",
"for unloggedIn players, set this setting to true.",
"Default is false."
})
public static final Property<Boolean> ENABLE_PERMISSION_CHECK =
newProperty("permission.EnablePermissionCheck", false);
private PluginSettings() {
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class ProtectionSettings implements SettingsClass {
@Comment("Enable some servers protection (country based login, antibot)")
public static final Property<Boolean> ENABLE_PROTECTION =
newProperty(BOOLEAN, "Protection.enableProtection", false);
@Comment({"Countries allowed to join the server and register, see http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ for countries' codes",
"PLEASE USE QUOTES!"})
public static final Property<List<String>> COUNTRIES_WHITELIST =
newProperty(STRING_LIST, "Protection.countries", "US", "GB", "A1");
@Comment({"Countries not allowed to join the server and register",
"PLEASE USE QUOTES!"})
public static final Property<List<String>> COUNTRIES_BLACKLIST =
newProperty(STRING_LIST, "Protection.countriesBlacklist");
@Comment("Do we need to enable automatic antibot system?")
public static final Property<Boolean> ENABLE_ANTIBOT =
newProperty(BOOLEAN, "Protection.enableAntiBot", false);
@Comment("Max number of player allowed to login in 5 secs before enable AntiBot system automatically")
public static final Property<Integer> ANTIBOT_SENSIBILITY =
newProperty(INTEGER, "Protection.antiBotSensibility", 5);
@Comment("Duration in minutes of the antibot automatic system")
public static final Property<Integer> ANTIBOT_DURATION =
newProperty(INTEGER, "Protection.antiBotDuration", 10);
private ProtectionSettings() {
}
}

View File

@ -0,0 +1,49 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.domain.PropertyType.STRING;
public class PurgeSettings implements SettingsClass {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
public static final Property<Boolean> USE_AUTO_PURGE =
newProperty(BOOLEAN, "Purge.useAutoPurge", false);
@Comment("Number of Days an account become Unused")
public static final Property<Integer> DAYS_BEFORE_REMOVE_PLAYER =
newProperty(INTEGER, "Purge.daysBeforeRemovePlayer", 60);
@Comment("Do we need to remove the player.dat file during purge process?")
public static final Property<Boolean> REMOVE_PLAYER_DAT =
newProperty(BOOLEAN, "Purge.removePlayerDat", false);
@Comment("Do we need to remove the Essentials/users/player.yml file during purge process?")
public static final Property<Boolean> REMOVE_ESSENTIALS_FILES =
newProperty(BOOLEAN, "Purge.removeEssentialsFile", false);
@Comment("World where are players.dat stores")
public static final Property<String> DEFAULT_WORLD =
newProperty(STRING, "Purge.defaultWorld", "world");
@Comment("Do we need to remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge process ?")
public static final Property<Boolean> REMOVE_LIMITED_CREATIVE_INVENTORIES =
newProperty(BOOLEAN, "Purge.removeLimitedCreativesInventories", false);
@Comment("Do we need to remove the AntiXRayData/PlayerData/player file during purge process?")
public static final Property<Boolean> REMOVE_ANTI_XRAY_FILE =
newProperty(BOOLEAN, "Purge.removeAntiXRayFile", false);
@Comment("Do we need to remove permissions?")
public static final Property<Boolean> REMOVE_PERMISSIONS =
newProperty(BOOLEAN, "Purge.removePermissions", false);
private PurgeSettings() {
}
}

View File

@ -0,0 +1,101 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class RegistrationSettings implements SettingsClass {
@Comment("Enable registration on the server?")
public static final Property<Boolean> IS_ENABLED =
newProperty("settings.registration.enabled", true);
@Comment({
"Send every X seconds a message to a player to",
"remind him that he has to login/register"})
public static final Property<Integer> MESSAGE_INTERVAL =
newProperty("settings.registration.messageInterval", 5);
@Comment({
"Only registered and logged in players can play.",
"See restrictions for exceptions"})
public static final Property<Boolean> FORCE =
newProperty("settings.registration.force", true);
@Comment("Do we replace password registration by an email registration method?")
public static final Property<Boolean> USE_EMAIL_REGISTRATION =
newProperty("settings.registration.enableEmailRegistrationSystem", false);
@Comment({
"Enable double check of email when you register",
"when it's true, registration requires that kind of command:",
"/register <email> <confirmEmail>"})
public static final Property<Boolean> ENABLE_CONFIRM_EMAIL =
newProperty("settings.registration.doubleEmailCheck", false);
@Comment({
"Do we force kicking player after a successful registration?",
"Do not use with login feature below"})
public static final Property<Boolean> FORCE_KICK_AFTER_REGISTER =
newProperty("settings.registration.forceKickAfterRegister", false);
@Comment("Does AuthMe need to enforce a /login after a successful registration?")
public static final Property<Boolean> FORCE_LOGIN_AFTER_REGISTER =
newProperty("settings.registration.forceLoginAfterRegister", false);
@Comment("Force these commands after /login, without any '/', use %p to replace with player name")
public static final Property<List<String>> FORCE_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.forceCommands");
@Comment("Force these commands after /login as service console, without any '/'. "
+ "Use %p to replace with player name")
public static final Property<List<String>> FORCE_COMMANDS_AS_CONSOLE =
newProperty(PropertyType.STRING_LIST, "settings.forceCommandsAsConsole");
@Comment("Force these commands after /register, without any '/', use %p to replace with player name")
public static final Property<List<String>> FORCE_REGISTER_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.forceRegisterCommands");
@Comment("Force these commands after /register as a server console, without any '/'. "
+ "Use %p to replace with player name")
public static final Property<List<String>> FORCE_REGISTER_COMMANDS_AS_CONSOLE =
newProperty(PropertyType.STRING_LIST, "settings.forceRegisterCommandsAsConsole");
@Comment({
"Enable to display the welcome message (welcome.txt) after a registration or a login",
"You can use colors in this welcome.txt + some replaced strings:",
"{PLAYER}: player name, {ONLINE}: display number of online players, {MAXPLAYERS}: display server slots,",
"{IP}: player ip, {LOGINS}: number of players logged, {WORLD}: player current world, {SERVER}: server name",
"{VERSION}: get current bukkit version, {COUNTRY}: player country"})
public static final Property<Boolean> USE_WELCOME_MESSAGE =
newProperty("settings.useWelcomeMessage", true);
@Comment("Do we need to broadcast the welcome message to all server or only to the player? set true for "
+ "server or false for player")
public static final Property<Boolean> BROADCAST_WELCOME_MESSAGE =
newProperty("settings.broadcastWelcomeMessage", false);
@Comment("Do we need to delay the join/leave message to be displayed only when the player is authenticated?")
public static final Property<Boolean> DELAY_JOIN_LEAVE_MESSAGES =
newProperty("settings.delayJoinLeaveMessages", true);
@Comment("Do we need to add potion effect Blinding before login/reigster?")
public static final Property<Boolean> APPLY_BLIND_EFFECT =
newProperty("settings.applyBlindEffect", false);
@Comment({
"Do we need to prevent people to login with another case?",
"If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI"})
public static final Property<Boolean> PREVENT_OTHER_CASE =
newProperty("settings.preventOtherCase", false);
private RegistrationSettings() {
}
}

View File

@ -0,0 +1,184 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
public class RestrictionSettings implements SettingsClass {
@Comment({
"Can not authenticated players chat and see the chat log?",
"Keep in mind that this feature also blocks all commands not",
"listed in the list below."})
public static final Property<Boolean> ALLOW_CHAT =
newProperty("settings.restrictions.allowChat", false);
@Comment("Allowed commands for unauthenticated players")
public static final Property<List<String>> ALLOW_COMMANDS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.allowCommands",
"login", "register", "l", "reg", "email", "captcha");
@Comment("Max number of allowed registrations per IP")
// TODO ljacqu 20160109: If 0 == unlimited, add this fact ot the comment
public static final Property<Integer> MAX_REGISTRATION_PER_IP =
newProperty("settings.restrictions.maxRegPerIp", 1);
@Comment("Minimum allowed username length")
public static final Property<Integer> MIN_NICKNAME_LENGTH =
newProperty("settings.restrictions.minNicknameLength", 4);
@Comment("Maximum allowed username length")
public static final Property<Integer> MAX_NICKNAME_LENGTH =
newProperty("settings.restrictions.maxNicknameLength", 16);
@Comment({
"When this setting is enabled, online players can't be kicked out",
"due to \"Logged in from another Location\"",
"This setting will prevent potential security exploits."})
public static final Property<Boolean> FORCE_SINGLE_SESSION =
newProperty("settings.restrictions.ForceSingleSession", true);
@Comment({
"If enabled, every player will be teleported to the world spawnpoint",
"after successful authentication.",
"The quit location of the player will be overwritten.",
"This is different from \"teleportUnAuthedToSpawn\" that teleport player",
"back to his quit location after the authentication."})
public static final Property<Boolean> FORCE_SPAWN_LOCATION_AFTER_LOGIN =
newProperty("settings.restrictions.ForceSpawnLocOnJoinEnabled", false);
@Comment("This option will save the quit location of the players.")
public static final Property<Boolean> SAVE_QUIT_LOCATION =
newProperty("settings.restrictions.SaveQuitLocation", false);
@Comment({
"To activate the restricted user feature you need",
"to enable this option and configure the AllowedRestrctedUser field."})
public static final Property<Boolean> ENABLE_RESTRICTED_USERS =
newProperty("settings.restrictions.AllowRestrictedUser", false);
@Comment({
"The restricted user feature will kick players listed below",
"if they don't match the defined IP address.",
"Example:",
" AllowedRestrictedUser:",
" - playername;127.0.0.1"})
public static final Property<List<String>> ALLOWED_RESTRICTED_USERS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.AllowedRestrictedUser");
@Comment("Should unregistered players be kicked immediately?")
public static final Property<Boolean> KICK_NON_REGISTERED =
newProperty("settings.restrictions.kickNonRegistered", false);
@Comment("Should players be kicked on wrong password?")
public static final Property<Boolean> KICK_ON_WRONG_PASSWORD =
newProperty("settings.restrictions.kickOnWrongPassword", false);
@Comment({
"Should not logged in players be teleported to the spawn?",
"After the authentication they will be teleported back to",
"their normal position."})
public static final Property<Boolean> TELEPORT_UNAUTHED_TO_SPAWN =
newProperty("settings.restrictions.teleportUnAuthedToSpawn", false);
@Comment("Can unregistered players walk around?")
public static final Property<Boolean> ALLOW_UNAUTHED_MOVEMENT =
newProperty("settings.restrictions.allowMovement", false);
@Comment({
"Should not authenticated players have speed = 0?",
"This will reset the fly/walk speed to default value after the login."})
public static final Property<Boolean> REMOVE_SPEED =
newProperty("settings.restrictions.removeSpeed", true);
@Comment({
"After how many seconds should players who fail to login or register",
"be kicked? Set to 0 to disable."})
public static final Property<Integer> TIMEOUT =
newProperty("settings.restrictions.timeout", 30);
@Comment("Regex syntax of allowed characters in the player name.")
public static final Property<String> ALLOWED_NICKNAME_CHARACTERS =
newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*");
@Comment({
"How far can unregistered players walk?",
"Set to 0 for unlimited radius"
})
public static final Property<Integer> ALLOWED_MOVEMENT_RADIUS =
newProperty("settings.restrictions.allowedMovementRadius", 100);
@Comment({
"Enable double check of password when you register",
"when it's true, registration requires that kind of command:",
"/register <password> <confirmPassword>"})
public static final Property<Boolean> ENABLE_PASSWORD_CONFIRMATION =
newProperty("settings.restrictions.enablePasswordConfirmation", true);
@Comment("Should we protect the player inventory before logging in?")
public static final Property<Boolean> PROTECT_INVENTORY_BEFORE_LOGIN =
newProperty("settings.restrictions.ProtectInventoryBeforeLogIn", true);
@Comment({
"Should we display all other accounts from a player when he joins?",
"permission: /authme.admin.accounts"})
public static final Property<Boolean> DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true);
@Comment({
"WorldNames where we need to force the spawn location for ForceSpawnLocOnJoinEnabled",
"Case-sensitive!"})
public static final Property<List<String>> FORCE_SPAWN_ON_WORLDS =
newProperty(PropertyType.STRING_LIST, "settings.restrictions.ForceSpawnOnTheseWorlds",
"world", "world_nether", "world_the_end");
@Comment("Ban ip when the ip is not the ip registered in database")
public static final Property<Boolean> BAN_UNKNOWN_IP =
newProperty("settings.restrictions.banUnsafedIP", false);
@Comment("Spawn priority; values: authme, essentials, multiverse, default")
public static final Property<String> SPAWN_PRIORITY =
newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");
@Comment("Maximum Login authorized by IP")
public static final Property<Integer> MAX_LOGIN_PER_IP =
newProperty("settings.restrictions.maxLoginPerIp", 0);
@Comment("Maximum Join authorized by IP")
public static final Property<Integer> MAX_JOIN_PER_IP =
newProperty("settings.restrictions.maxJoinPerIp", 0);
@Comment("AuthMe will NEVER teleport players if set to true!")
public static final Property<Boolean> NO_TELEPORT =
newProperty("settings.restrictions.noTeleport", false);
@Comment("Regex syntax for allowed chars in passwords")
public static final Property<String> ALLOWED_PASSWORD_REGEX =
newProperty("settings.restrictions.allowedPasswordCharacters", "[\\x21-\\x7E]*");
@Comment("Force survival gamemode when player joins?")
public static final Property<Boolean> FORCE_SURVIVAL_MODE =
newProperty("settings.GameMode.ForceSurvivalMode", false);
@Comment({
"Below you can list all account names that",
"AuthMe will ignore for registration or login, configure it",
"at your own risk!! Remember that if you are going to add",
"nickname with [], you have to delimit name with ' '.",
"this option add compatibility with BuildCraft and some",
"other mods.",
"It is case-sensitive!"
})
public static final Property<List<String>> UNRESTRICTED_NAMES =
newProperty(PropertyType.STRING_LIST, "settings.unrestrictions.UnrestrictedName");
private RestrictionSettings() {
}
}

View File

@ -0,0 +1,103 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static fr.xephi.authme.settings.domain.PropertyType.STRING_LIST;
public class SecuritySettings implements SettingsClass {
@Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set this to false,",
"AuthMe will automatically disable and the server won't be protected!"})
public static final Property<Boolean> STOP_SERVER_ON_PROBLEM =
newProperty("Security.SQLProblem.stopServer", true);
@Comment("/reload support")
public static final Property<Boolean> USE_RELOAD_COMMAND_SUPPORT =
newProperty("Security.ReloadCommand.useReloadCommandSupport", true);
@Comment("Remove spam from console?")
public static final Property<Boolean> REMOVE_SPAM_FROM_CONSOLE =
newProperty("Security.console.noConsoleSpam", false);
@Comment("Remove passwords from console?")
public static final Property<Boolean> REMOVE_PASSWORD_FROM_CONSOLE =
newProperty("Security.console.removePassword", true);
@Comment("Player need to put a captcha when he fails too lot the password")
public static final Property<Boolean> USE_CAPTCHA =
newProperty("Security.captcha.useCaptcha", false);
@Comment("Max allowed tries before request a captcha")
public static final Property<Integer> MAX_LOGIN_TRIES_BEFORE_CAPTCHA =
newProperty("Security.captcha.maxLoginTry", 5);
@Comment("Captcha length")
public static final Property<Integer> CAPTCHA_LENGTH =
newProperty("Security.captcha.captchaLength", 5);
@Comment({"Kick players before stopping the server, that allow us to save position of players",
"and all needed information correctly without any corruption."})
public static final Property<Boolean> KICK_PLAYERS_BEFORE_STOPPING =
newProperty("Security.stop.kickPlayersBeforeStopping", true);
@Comment("Minimum length of password")
public static final Property<Integer> MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5);
@Comment("Maximum length of password")
public static final Property<Integer> 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<String> UNLOGGEDIN_GROUP =
newProperty("settings.security.unLoggedinGroup", "unLoggedinGroup");
@Comment({
"Possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB,",
"MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512,",
"DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only)"
})
public static final Property<HashAlgorithm> PASSWORD_HASH =
newProperty(HashAlgorithm.class, "settings.security.passwordHash", HashAlgorithm.SHA256);
@Comment("Salt length for the SALTED2MD5 MD5(MD5(password)+salt)")
public static final Property<Integer> DOUBLE_MD5_SALT_LENGTH =
newProperty("settings.security.doubleMD5SaltLength", 8);
@Comment({"If password checking return false, do we need to check with all",
"other password algorithm to check an old password?",
"AuthMe will update the password to the new password hash"})
public static final Property<Boolean> SUPPORT_OLD_PASSWORD_HASH =
newProperty("settings.security.supportOldPasswordHash", false);
@Comment({"Prevent unsafe passwords from being used; put them in lowercase!",
"unsafePasswords:",
"- '123456'",
"- 'password'"})
public static final Property<List<String>> UNSAFE_PASSWORDS =
newProperty(STRING_LIST, "settings.security.unsafePasswords",
"123456", "password", "qwerty", "12345", "54321");
private SecuritySettings() {
}
}

View File

@ -0,0 +1,69 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
/**
* Utility class responsible for the retrieval of all {@link Property} fields via reflections.
*/
final class SettingsFieldRetriever {
/** The classes to scan for properties. */
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
ConverterSettings.class, PluginSettings.class, RestrictionSettings.class,
DatabaseSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class,
RegistrationSettings.class, BackupSettings.class);
private SettingsFieldRetriever() {
}
/**
* Scan all given classes for their properties and return the generated {@link PropertyMap}.
*
* @return PropertyMap containing all found properties and their associated comments
* @see #CONFIGURATION_CLASSES
*/
public static PropertyMap getAllPropertyFields() {
PropertyMap properties = new PropertyMap();
for (Class<?> clazz : CONFIGURATION_CLASSES) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
Property property = getFieldIfRelevant(field);
if (property != null) {
properties.put(property, getCommentsForField(field));
}
}
}
return properties;
}
private static String[] getCommentsForField(Field field) {
if (field.isAnnotationPresent(Comment.class)) {
return field.getAnnotation(Comment.class).value();
}
return new String[0];
}
private static Property<?> getFieldIfRelevant(Field field) {
field.setAccessible(true);
if (field.isAccessible() && Property.class.equals(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
return (Property) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not fetch field '" + field.getName() + "' from class '"
+ field.getDeclaringClass().getSimpleName() + "': " + StringUtils.formatException(e));
}
}
return null;
}
}

View File

@ -0,0 +1,17 @@
package fr.xephi.authme.settings.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Comment for properties which are also included in the YAML file upon saving.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment {
String[] value();
}

View File

@ -0,0 +1,50 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.List;
import static java.util.Arrays.asList;
/**
* Enum property type.
* @param <E> The enum class
*/
class EnumPropertyType<E extends Enum<E>> extends PropertyType<E> {
private Class<E> clazz;
public EnumPropertyType(Class<E> clazz) {
this.clazz = clazz;
}
@Override
public E getFromFile(Property<E> property, FileConfiguration configuration) {
String textValue = configuration.getString(property.getPath());
if (textValue == null) {
return property.getDefaultValue();
}
E mappedValue = mapToEnum(textValue);
return mappedValue != null ? mappedValue : property.getDefaultValue();
}
@Override
protected List<String> asYaml(E value) {
return asList("'" + value + "'");
}
@Override
public boolean contains(Property<E> property, FileConfiguration configuration) {
return super.contains(property, configuration)
&& mapToEnum(configuration.getString(property.getPath())) != null;
}
private E mapToEnum(String value) {
for (E entry : clazz.getEnumConstants()) {
if (entry.name().equalsIgnoreCase(value)) {
return entry;
}
}
return null;
}
}

View File

@ -0,0 +1,114 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Properties (i.e. a <i>setting</i> that is read from the config.yml file).
*/
public class Property<T> {
private final PropertyType<T> type;
private final String path;
private final T defaultValue;
private Property(PropertyType<T> type, String path, T defaultValue) {
Objects.requireNonNull(defaultValue);
this.type = type;
this.path = path;
this.defaultValue = defaultValue;
}
public static <T> Property<T> newProperty(PropertyType<T> type, String path, T defaultValue) {
return new Property<>(type, path, defaultValue);
}
@SafeVarargs
public static <U> Property<List<U>> newProperty(PropertyType<List<U>> type, String path, U... defaultValues) {
return new Property<>(type, path, Arrays.asList(defaultValues));
}
public static <E extends Enum<E>> Property<E> newProperty(Class<E> clazz, String path, E defaultValue) {
return new Property<>(new EnumPropertyType<>(clazz), path, defaultValue);
}
// -----
// Overloaded convenience methods for specific types
// -----
public static Property<Boolean> newProperty(String path, boolean defaultValue) {
return new Property<>(PropertyType.BOOLEAN, path, defaultValue);
}
public static Property<Integer> newProperty(String path, int defaultValue) {
return new Property<>(PropertyType.INTEGER, path, defaultValue);
}
public static Property<String> newProperty(String path, String defaultValue) {
return new Property<>(PropertyType.STRING, path, defaultValue);
}
// -----
// Hooks to the PropertyType methods
// -----
/**
* Get the property value from the given configuration.
*
* @param configuration The configuration to read the value from
* @return The value, or default if not present
*/
public T getFromFile(FileConfiguration configuration) {
return type.getFromFile(this, configuration);
}
/**
* Format the property value as YAML.
*
* @param configuration The configuration to read the value from
* @return The property value as YAML
*/
public List<String> formatValueAsYaml(FileConfiguration configuration) {
return type.asYaml(this, configuration);
}
/**
* Return whether or not the given configuration file contains the property.
*
* @param configuration The configuration file to verify
* @return True if the property is present, false otherwise
*/
public boolean isPresent(FileConfiguration configuration) {
return type.contains(this, configuration);
}
// -----
// Trivial getters
// -----
/**
* Return the default value of the property.
*
* @return The default value
*/
public T getDefaultValue() {
return defaultValue;
}
/**
* Return the property path (i.e. the node at which this property is located in the YAML file).
*
* @return The path
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "Property '" + path + "'";
}
}

View File

@ -0,0 +1,162 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
/**
* Handles a certain property type and provides type-specific functionality.
*
* @param <T> The value of the property
* @see Property
*/
public abstract class PropertyType<T> {
public static final PropertyType<Boolean> BOOLEAN = new BooleanProperty();
public static final PropertyType<Double> DOUBLE = new DoubleProperty();
public static final PropertyType<Integer> INTEGER = new IntegerProperty();
public static final PropertyType<String> STRING = new StringProperty();
public static final PropertyType<List<String>> STRING_LIST = new StringListProperty();
/**
* Get the property's value from the given YAML configuration.
*
* @param property The property to retrieve
* @param configuration The YAML configuration to read from
* @return The read value, or the default value if absent
*/
public abstract T getFromFile(Property<T> property, FileConfiguration configuration);
/**
* Return the property's value (or its default) as YAML.
*
* @param property The property to transform
* @param configuration The YAML configuration to read from
* @return The read value or its default in YAML format
*/
public List<String> asYaml(Property<T> property, FileConfiguration configuration) {
return asYaml(getFromFile(property, configuration));
}
/**
* Return whether the property is present in the given configuration.
*
* @param property The property to search for
* @param configuration The configuration to verify
* @return True if the property is present, false otherwise
*/
public boolean contains(Property<T> property, FileConfiguration configuration) {
return configuration.contains(property.getPath());
}
/**
* Transform the given value to YAML.
*
* @param value The value to transform
* @return The value as YAML
*/
protected abstract List<String> asYaml(T value);
/**
* Boolean property.
*/
private static final class BooleanProperty extends PropertyType<Boolean> {
@Override
public Boolean getFromFile(Property<Boolean> property, FileConfiguration configuration) {
return configuration.getBoolean(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Boolean value) {
return asList(value ? "true" : "false");
}
}
/**
* Double property.
*/
private static final class DoubleProperty extends PropertyType<Double> {
@Override
public Double getFromFile(Property<Double> property, FileConfiguration configuration) {
return configuration.getDouble(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Double value) {
return asList(String.valueOf(value));
}
}
/**
* Integer property.
*/
private static final class IntegerProperty extends PropertyType<Integer> {
@Override
public Integer getFromFile(Property<Integer> property, FileConfiguration configuration) {
return configuration.getInt(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(Integer value) {
return asList(String.valueOf(value));
}
}
/**
* String property.
*/
private static final class StringProperty extends PropertyType<String> {
@Override
public String getFromFile(Property<String> property, FileConfiguration configuration) {
return configuration.getString(property.getPath(), property.getDefaultValue());
}
@Override
protected List<String> asYaml(String value) {
return asList(toYamlLiteral(value));
}
public static String toYamlLiteral(String str) {
// TODO: Need to handle new lines properly
return "'" + str.replace("'", "''") + "'";
}
}
/**
* String list property.
*/
private static final class StringListProperty extends PropertyType<List<String>> {
@Override
public List<String> getFromFile(Property<List<String>> property, FileConfiguration configuration) {
if (!configuration.isList(property.getPath())) {
return property.getDefaultValue();
}
return configuration.getStringList(property.getPath());
}
@Override
protected List<String> asYaml(List<String> value) {
if (value.isEmpty()) {
return asList("[]");
}
List<String> resultLines = new ArrayList<>();
resultLines.add(""); // add
for (String entry : value) {
// TODO: StringProperty#toYamlLiteral will return List<String>...
resultLines.add(" - " + StringProperty.toYamlLiteral(entry));
}
return resultLines;
}
@Override
public boolean contains(Property<List<String>> property, FileConfiguration configuration) {
return configuration.contains(property.getPath()) && configuration.isList(property.getPath());
}
}
}

View File

@ -0,0 +1,7 @@
package fr.xephi.authme.settings.domain;
/**
* Marker for classes that define {@link Property} fields.
*/
public interface SettingsClass {
}

View File

@ -0,0 +1,121 @@
package fr.xephi.authme.settings.propertymap;
import java.util.ArrayList;
import java.util.List;
/**
* Node class for building a tree from supplied String paths, ordered by insertion.
* <p>
* For instance, consider a tree to which the following paths are inserted (in the given order):
* "animal.bird.duck", "color.yellow", "animal.rodent.rat", "animal.rodent.rabbit", "color.red".
* For such a tree:<ul>
* <li>"animal" (or any of its children) is sorted before "color" (or any of its children)</li>
* <li>"animal.bird" or any child thereof is sorted before "animal.rodent"</li>
* <li>"animal.rodent.rat" comes before "animal.rodent.rabbit"</li>
* </ul>
*
* @see PropertyMapComparator
*/
final class Node {
private final String name;
private final List<Node> children;
private Node(String name) {
this.name = name;
this.children = new ArrayList<>();
}
/**
* Create a root node, i.e. the starting node for a new tree. Call this method to create
* a new tree and always pass this root to other methods.
*
* @return The generated root node.
*/
public static Node createRoot() {
return new Node(null);
}
/**
* Add a node to the root, creating any intermediary children that don't exist.
*
* @param root The root to add the path to
* @param fullPath The entire path of the node to add, separate by periods
*/
public static void addNode(Node root, String fullPath) {
String[] pathParts = fullPath.split("\\.");
Node parent = root;
for (String part : pathParts) {
Node child = parent.getChild(part);
if (child == null) {
child = new Node(part);
parent.children.add(child);
}
parent = child;
}
}
/**
* Compare two nodes by this class' sorting behavior (insertion order).
* Note that this method assumes that both supplied paths exist in the tree.
*
* @param root The root of the tree
* @param fullPath1 The full path to the first node
* @param fullPath2 The full path to the second node
* @return The comparison result, in the same format as {@link Comparable#compareTo}
*/
public static int compare(Node root, String fullPath1, String fullPath2) {
String[] path1 = fullPath1.split("\\.");
String[] path2 = fullPath2.split("\\.");
int commonCount = 0;
Node commonNode = root;
while (commonCount < path1.length && commonCount < path2.length
&& path1[commonCount].equals(path2[commonCount]) && commonNode != null) {
commonNode = commonNode.getChild(path1[commonCount]);
++commonCount;
}
if (commonNode == null) {
System.err.println("Could not find common node for '" + fullPath1 + "' at index " + commonCount);
return fullPath1.compareTo(fullPath2); // fallback
} else if (commonCount >= path1.length || commonCount >= path2.length) {
return Integer.compare(path1.length, path2.length);
}
int child1Index = commonNode.getChildIndex(path1[commonCount]);
int child2Index = commonNode.getChildIndex(path2[commonCount]);
return Integer.compare(child1Index, child2Index);
}
private Node getChild(String name) {
for (Node child : children) {
if (child.name.equals(name)) {
return child;
}
}
return null;
}
/**
* Return the child's index, i.e. the position at which it was inserted to its parent.
*
* @param name The name of the node
* @return The insertion index
*/
private int getChildIndex(String name) {
int i = 0;
for (Node child : children) {
if (child.name.equals(name)) {
return i;
}
++i;
}
return -1;
}
@Override
public String toString() {
return "Node '" + name + "'";
}
}

View File

@ -0,0 +1,66 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Class wrapping a {@code Map<Property, String[]>} for storing properties and their associated
* comments with custom ordering.
*
* @see PropertyMapComparator for details about the map's order
*/
public class PropertyMap {
private Map<Property<?>, String[]> map;
private PropertyMapComparator comparator;
/**
* Create a new property map.
*/
public PropertyMap() {
comparator = new PropertyMapComparator();
map = new TreeMap<>(comparator);
}
/**
* Add a new property to the map.
*
* @param property The property to add
* @param comments The comments associated to the property
*/
public void put(Property property, String[] comments) {
comparator.add(property);
map.put(property, comments);
}
/**
* Return the entry set of the map.
*
* @return The entry set
*/
public Set<Map.Entry<Property<?>, String[]>> entrySet() {
return map.entrySet();
}
/**
* Return the key set of the map, i.e. all property objects it holds.
*
* @return The key set
*/
public Set<Property<?>> keySet() {
return map.keySet();
}
/**
* Return the size of the map.
*
* @return The size
*/
public int size() {
return map.size();
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Comparator;
/**
* Custom comparator for {@link PropertyMap}. It guarantees that the map's entries:
* <ul>
* <li>are grouped by path, e.g. all "DataSource.mysql" properties are together, and "DataSource.mysql" properties
* are within the broader "DataSource" group.</li>
* <li>are ordered by insertion, e.g. if the first "DataSource" property is inserted before the first "security"
* property, then "DataSource" properties will come before the "security" ones.</li>
* </ul>
*/
final class PropertyMapComparator implements Comparator<Property> {
private Node parent = Node.createRoot();
/**
* Method to call when adding a new property to the map (as to retain its insertion time).
*
* @param property The property that is being added
*/
public void add(Property property) {
Node.addNode(parent, property.getPath());
}
@Override
public int compare(Property p1, Property p2) {
return Node.compare(parent, p1.getPath(), p2.getPath());
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
}

View File

@ -3,6 +3,7 @@ package fr.xephi.authme.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* Utils class for collections.
@ -58,4 +59,15 @@ public final class CollectionUtils {
public static <T> boolean isEmpty(Collection<T> coll) {
return coll == null || coll.isEmpty();
}
public static <T> List<T> filterCommonStart(List<T> list1, List<T> list2) {
List<T> commonStart = new ArrayList<>();
int minSize = Math.min(list1.size(), list2.size());
int i = 0;
while (i < minSize && Objects.equals(list1.get(i), list2.get(i))) {
commonStart.add(list1.get(i));
++i;
}
return commonStart;
}
}

View File

@ -156,8 +156,10 @@ settings:
# ForceSurvivalMode to player when join ?
ForceSurvivalMode: false
security:
# minimum Length of password
# Minimum length of password
minPasswordLength: 5
# Maximum length of password
passwordMaxLength: 30
# this is very important options,
# every time player join the server,
# if they are registered, AuthMe will switch him
@ -209,7 +211,7 @@ settings:
# Only registered and logged in players can play.
# See restrictions for exceptions
force: true
# Does we replace password registration by an Email registration method?
# Do we replace password registration by an email registration method?
enableEmailRegistrationSystem: false
# Enable double check of email when you register
# when it's true, registration require that kind of command:
@ -340,8 +342,6 @@ Email:
RecoveryPasswordLength: 8
# Email subject of password get
mailSubject: 'Your new AuthMe Password'
# Email text here
mailText: 'Dear <playername>, <br /><br /> This is your new AuthMe password for the server <br /><br /> <servername> : <br /><br /> <generatedpass><br /><image><br />Do not forget to change password after login! <br /> /changepassword <generatedpass> newPassword'
# Like maxRegPerIp but with email
maxRegPerEmail: 1
# Recall players to add an email?
@ -355,6 +355,8 @@ Email:
emailWhitelisted: []
# Do we need to send new password draw in an image?
generateImage: false
# The email OAuth 2 token (leave empty if not used)
emailOauth2Token: ''
Hooks:
# Do we need to hook with multiverse for spawn checking?
multiverse: true

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.command;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.command.CommandSender;
import org.junit.Before;
import org.junit.Test;
@ -7,20 +8,27 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.List;
import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS;
import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.NO_PERMISSION;
import static fr.xephi.authme.command.FoundResultStatus.SUCCESS;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
@ -89,7 +97,138 @@ public class CommandHandlerTest {
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
verify(serviceMock).outputMappingError(eq(sender), any(FoundCommandResult.class));
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("don't have permission"));
}
@Test
public void shouldNotCallExecutableForWrongArguments() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS));
PermissionsManager permissionsManager = mock(PermissionsManager.class);
given(permissionsManager.hasPermission(sender, command)).willReturn(true);
given(serviceMock.getPermissionsManager()).willReturn(permissionsManager);
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, atLeastOnce()).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Incorrect command arguments"));
}
@Test
public void shouldNotCallExecutableForWrongArgumentsAndPermissionDenied() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, INCORRECT_ARGUMENTS));
PermissionsManager permissionsManager = mock(PermissionsManager.class);
given(permissionsManager.hasPermission(sender, command)).willReturn(false);
given(serviceMock.getPermissionsManager()).willReturn(permissionsManager);
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("You don't have permission"));
}
@Test
public void shouldNotCallExecutableForFailedParsing() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.0, MISSING_BASE_COMMAND));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender).sendMessage(captor.capture());
assertThat(captor.getValue(), containsString("Failed to parse"));
}
@Test
public void shouldNotCallExecutableForUnknownLabelAndHaveSuggestion() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(command.getLabels()).willReturn(Collections.singletonList("test_cmd"));
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 0.01, UNKNOWN_LABEL));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, times(3)).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Unknown command"));
assertThat(captor.getAllValues().get(1), containsString("Did you mean"));
assertThat(captor.getAllValues().get(1), containsString("/test_cmd"));
assertThat(captor.getAllValues().get(2), containsString("Use the command"));
assertThat(captor.getAllValues().get(2), containsString("to view help"));
}
@Test
public void shouldNotCallExecutableForUnknownLabelAndNotSuggestCommand() {
// given
String bukkitLabel = "unreg";
String[] bukkitArgs = {"testPlayer"};
CommandSender sender = mock(CommandSender.class);
CommandDescription command = mock(CommandDescription.class);
given(command.getLabels()).willReturn(Collections.singletonList("test_cmd"));
given(serviceMock.mapPartsToCommand(any(CommandSender.class), anyListOf(String.class))).willReturn(
new FoundCommandResult(command, asList("unreg"), asList("testPlayer"), 1.0, UNKNOWN_LABEL));
// when
handler.processCommand(sender, bukkitLabel, bukkitArgs);
// then
verify(serviceMock).mapPartsToCommand(eq(sender), captor.capture());
assertThat(captor.getValue(), contains("unreg", "testPlayer"));
verify(command, never()).getExecutableCommand();
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, times(2)).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), containsString("Unknown command"));
assertThat(captor.getAllValues().get(1), containsString("Use the command"));
assertThat(captor.getAllValues().get(1), containsString("to view help"));
}
@Test

View File

@ -1,7 +1,5 @@
package fr.xephi.authme.command;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.command.CommandSender;
import org.junit.Before;
@ -43,7 +41,7 @@ public class CommandMapperTest {
@Before
public void setUpMocks() {
permissionsManagerMock = mock(PermissionsManager.class);
mapper = new CommandMapper(commands, mock(Messages.class), permissionsManagerMock, mock(HelpProvider.class));
mapper = new CommandMapper(commands, permissionsManagerMock);
}
// -----------
@ -256,4 +254,23 @@ public class CommandMapperTest {
assertThat(result.getDifference(), equalTo(0.0));
}
@Test
public void shouldRecognizeMissingPermissionForCommand() {
// given
List<String> parts = Arrays.asList("authme", "login", "test1");
CommandSender sender = mock(CommandSender.class);
given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(false);
// when
FoundCommandResult result = mapper.mapPartsToCommand(sender, parts);
// then
assertThat(result.getCommandDescription(), equalTo(getCommandWithLabel(commands, "authme", "login")));
assertThat(result.getResultStatus(), equalTo(FoundResultStatus.NO_PERMISSION));
assertThat(result.getArguments(), contains("test1"));
assertThat(result.getDifference(), equalTo(0.0));
assertThat(result.getLabels(), equalTo(parts.subList(0, 2)));
assertThat(result.getArguments(), contains(parts.get(2)));
}
}

View File

@ -8,6 +8,9 @@ import fr.xephi.authme.output.Messages;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.settings.custom.NewSetting;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.junit.Before;
@ -36,6 +39,8 @@ public class CommandServiceTest {
private Messages messages;
private PasswordSecurity passwordSecurity;
private CommandService commandService;
private PermissionsManager permissionsManager;
private NewSetting settings;
@Before
public void setUpService() {
@ -44,7 +49,10 @@ public class CommandServiceTest {
helpProvider = mock(HelpProvider.class);
messages = mock(Messages.class);
passwordSecurity = mock(PasswordSecurity.class);
commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity);
permissionsManager = mock(PermissionsManager.class);
settings = mock(NewSetting.class);
commandService = new CommandService(
authMe, commandMapper, helpProvider, messages, passwordSecurity, permissionsManager, settings);
}
@Test
@ -87,19 +95,6 @@ public class CommandServiceTest {
verify(commandMapper).mapPartsToCommand(sender, commandParts);
}
@Test
public void shouldOutputMappingError() {
// given
CommandSender sender = mock(CommandSender.class);
FoundCommandResult result = mock(FoundCommandResult.class);
// when
commandService.outputMappingError(sender, result);
// then
verify(commandMapper).outputStandardError(sender, result);
}
@Test
@Ignore
public void shouldRunTaskInAsync() {
@ -169,16 +164,11 @@ public class CommandServiceTest {
@Test
public void shouldReturnPermissionsManager() {
// given
PermissionsManager manager = mock(PermissionsManager.class);
given(authMe.getPermissionsManager()).willReturn(manager);
// when
// given / when
PermissionsManager result = commandService.getPermissionsManager();
// then
assertThat(result, equalTo(manager));
verify(authMe).getPermissionsManager();
assertThat(result, equalTo(permissionsManager));
}
@Test
@ -195,4 +185,18 @@ public class CommandServiceTest {
assertThat(result, equalTo(givenMessages));
verify(messages).retrieve(key);
}
@Test
public void shouldRetrieveProperty() {
// given
Property<Integer> property = SecuritySettings.CAPTCHA_LENGTH;
given(settings.getProperty(property)).willReturn(7);
// when
int result = settings.getProperty(property);
// then
assertThat(result, equalTo(7));
verify(settings).getProperty(property);
}
}

View File

@ -5,13 +5,12 @@ import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.output.Messages;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.util.WrapperMock;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
@ -20,6 +19,7 @@ import java.util.Collections;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -35,8 +35,8 @@ public class CaptchaCommandTest {
@Before
public void setUpWrapperMock() {
wrapperMock = WrapperMock.createInstance();
Settings.useCaptcha = true;
commandService = mock(CommandService.class);
given(commandService.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(true);
}
@Test
@ -46,7 +46,7 @@ public class CaptchaCommandTest {
ExecutableCommand command = new CaptchaCommand();
// when
command.executeCommand(sender, new ArrayList<String>(), mock(CommandService.class));
command.executeCommand(sender, new ArrayList<String>(), commandService);
// then
assertThat(wrapperMock.wasMockCalled(AuthMe.class), equalTo(false));
@ -54,14 +54,14 @@ public class CaptchaCommandTest {
}
@Test
@Ignore
public void shouldRejectIfCaptchaIsNotUsed() {
// given
Player player = mockPlayerWithName("testplayer");
ExecutableCommand command = new CaptchaCommand();
given(commandService.getProperty(SecuritySettings.USE_CAPTCHA)).willReturn(false);
// when
command.executeCommand(player, Collections.singletonList("1234"), mock(CommandService.class));
command.executeCommand(player, Collections.singletonList("1234"), commandService);
// then
verify(commandService).send(player, MessageKey.USAGE_LOGIN);

View File

@ -4,7 +4,8 @@ import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.cache.auth.PlayerCache;
import fr.xephi.authme.command.CommandService;
import fr.xephi.authme.output.MessageKey;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.custom.RestrictionSettings;
import fr.xephi.authme.settings.custom.SecuritySettings;
import fr.xephi.authme.task.ChangePasswordTask;
import fr.xephi.authme.util.WrapperMock;
import org.bukkit.Server;
@ -19,9 +20,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@ -44,11 +45,11 @@ public class ChangePasswordCommandTest {
cacheMock = wrapperMock.getPlayerCache();
commandService = mock(CommandService.class);
when(commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).thenReturn(2);
when(commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).thenReturn(50);
// Only allow passwords with alphanumerical characters for the test
Settings.getPassRegex = "[a-zA-Z0-9]+";
Settings.getPasswordMinLen = 2;
Settings.passwordMaxLength = 50;
Settings.unsafePasswords = Collections.EMPTY_LIST;
when(commandService.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)).thenReturn("[a-zA-Z0-9]+");
when(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS)).thenReturn(Collections.EMPTY_LIST);
}
@Test
@ -112,7 +113,7 @@ public class ChangePasswordCommandTest {
// given
CommandSender sender = initPlayerWithName("abc12", true);
ChangePasswordCommand command = new ChangePasswordCommand();
Settings.passwordMaxLength = 3;
given(commandService.getProperty(SecuritySettings.MAX_PASSWORD_LENGTH)).willReturn(3);
// when
command.executeCommand(sender, Arrays.asList("12", "test"), commandService);
@ -127,7 +128,7 @@ public class ChangePasswordCommandTest {
// given
CommandSender sender = initPlayerWithName("abc12", true);
ChangePasswordCommand command = new ChangePasswordCommand();
Settings.getPasswordMinLen = 7;
given(commandService.getProperty(SecuritySettings.MIN_PASSWORD_LENGTH)).willReturn(7);
// when
command.executeCommand(sender, Arrays.asList("oldverylongpassword", "tester"), commandService);
@ -142,7 +143,8 @@ public class ChangePasswordCommandTest {
// given
CommandSender sender = initPlayerWithName("player", true);
ChangePasswordCommand command = new ChangePasswordCommand();
Settings.unsafePasswords = asList("test", "abc123");
given(commandService.getProperty(SecuritySettings.UNSAFE_PASSWORDS))
.willReturn(Arrays.asList("test", "abc123"));
// when
command.executeCommand(sender, Arrays.asList("oldpw", "abc123"), commandService);

View File

@ -0,0 +1,92 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.fail;
/**
* Test for {@link NewSetting} and the project's config.yml,
* verifying that no settings are missing from the file.
*/
public class ConfigFileConsistencyTest {
/** The file name of the project's sample config file. */
private static final String CONFIG_FILE = "/config.yml";
@Test
public void shouldHaveAllConfigs() throws IOException {
// given
URL url = this.getClass().getResource(CONFIG_FILE);
File configFile = new File(url.getFile());
NewSetting settings = new NewSetting(YamlConfiguration.loadConfiguration(configFile), new File("bogus"), null);
// when
boolean result = settings.containsAllSettings(SettingsFieldRetriever.getAllPropertyFields());
// then
if (!result) {
FileConfiguration configuration =
(FileConfiguration) ReflectionTestUtils.getFieldValue(NewSetting.class, settings, "configuration");
Set<String> knownProperties = getAllKnownPropertyPaths();
List<String> missingProperties = new ArrayList<>();
for (String path : knownProperties) {
if (!configuration.contains(path)) {
missingProperties.add(path);
}
}
fail("Found missing properties!\n-" + StringUtils.join("\n-", missingProperties));
}
}
@Test
public void shouldNotHaveUnknownConfigs() {
// given
URL url = this.getClass().getResource(CONFIG_FILE);
File configFile = new File(url.getFile());
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
Map<String, Object> allReadProperties = configuration.getValues(true);
Set<String> knownKeys = getAllKnownPropertyPaths();
// when
List<String> unknownPaths = new ArrayList<>();
for (Map.Entry<String, Object> entry : allReadProperties.entrySet()) {
// The value being a MemorySection means it's a parent node
if (!(entry.getValue() instanceof MemorySection) && !knownKeys.contains(entry.getKey())) {
unknownPaths.add(entry.getKey());
}
}
// then
if (!unknownPaths.isEmpty()) {
fail("Found " + unknownPaths.size() + " unknown property paths in the project's config.yml: \n- "
+ StringUtils.join("\n- ", unknownPaths));
}
}
private static Set<String> getAllKnownPropertyPaths() {
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
Set<String> paths = new HashSet<>(propertyMap.size());
for (Property<?> property : propertyMap.keySet()) {
paths.add(property.getPath());
}
return paths;
}
}

View File

@ -0,0 +1,115 @@
package fr.xephi.authme.settings.custom;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;
/**
* Integration test for {@link NewSetting}.
*/
public class NewSettingIntegrationTest {
/** File name of the sample config including all {@link TestConfiguration} values. */
private static final String COMPLETE_FILE = "config-sample-values.yml";
/** File name of the sample config missing certain {@link TestConfiguration} values. */
private static final String INCOMPLETE_FILE = "config-incomplete-sample.yml";
private static PropertyMap propertyMap;
@BeforeClass
public static void generatePropertyMap() {
propertyMap = new PropertyMap();
for (Field field : TestConfiguration.class.getDeclaredFields()) {
Object fieldValue = ReflectionTestUtils.getFieldValue(TestConfiguration.class, null, field.getName());
if (fieldValue instanceof Property<?>) {
Property<?> property = (Property<?>) fieldValue;
String[] comments = new String[]{"Comment for '" + property.getPath() + "'"};
propertyMap.put(property, comments);
}
}
}
@Test
public void shouldLoadAndReadAllProperties() {
// given
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(getConfigFile(COMPLETE_FILE));
File file = new File("unused");
assumeThat(file.exists(), equalTo(false));
// when / then
NewSetting settings = new NewSetting(configuration, file, propertyMap);
Map<Property<?>, Object> expectedValues = ImmutableMap.<Property<?>, Object>builder()
.put(TestConfiguration.DURATION_IN_SECONDS, 22)
.put(TestConfiguration.SYSTEM_NAME, "Custom sys name")
.put(TestConfiguration.RATIO_LIMIT, -4.1)
.put(TestConfiguration.RATIO_FIELDS, Arrays.asList("Australia", "Burundi", "Colombia"))
.put(TestConfiguration.VERSION_NUMBER, 2492)
.put(TestConfiguration.SKIP_BORING_FEATURES, false)
.put(TestConfiguration.BORING_COLORS, Arrays.asList("beige", "gray"))
.put(TestConfiguration.DUST_LEVEL, 0.81)
.put(TestConfiguration.USE_COOL_FEATURES, true)
.put(TestConfiguration.COOL_OPTIONS, Arrays.asList("Dinosaurs", "Explosions", "Big trucks"))
.build();
for (Map.Entry<Property<?>, Object> entry : expectedValues.entrySet()) {
assertThat("Property '" + entry.getKey().getPath() + "' has expected value",
settings.getProperty(entry.getKey()), equalTo(entry.getValue()));
}
assertThat(file.exists(), equalTo(false));
}
@Test
public void shouldWriteMissingProperties() {
// given/when
File file = getConfigFile(INCOMPLETE_FILE);
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(file);
assumeThat(configuration.contains(TestConfiguration.BORING_COLORS.getPath()), equalTo(false));
// Expectation: File is rewritten to since it does not have all configurations
new NewSetting(configuration, file, propertyMap);
// Load the settings again -> checks that what we wrote can be loaded again
configuration = YamlConfiguration.loadConfiguration(file);
// then
NewSetting settings = new NewSetting(configuration, file, propertyMap);
Map<Property<?>, Object> expectedValues = ImmutableMap.<Property<?>, Object>builder()
.put(TestConfiguration.DURATION_IN_SECONDS, 22)
.put(TestConfiguration.SYSTEM_NAME, "[TestDefaultValue]")
.put(TestConfiguration.RATIO_LIMIT, 3.0)
.put(TestConfiguration.RATIO_FIELDS, Arrays.asList("Australia", "Burundi", "Colombia"))
.put(TestConfiguration.VERSION_NUMBER, 32046)
.put(TestConfiguration.SKIP_BORING_FEATURES, false)
.put(TestConfiguration.BORING_COLORS, Collections.EMPTY_LIST)
.put(TestConfiguration.DUST_LEVEL, 0.2)
.put(TestConfiguration.USE_COOL_FEATURES, false)
.put(TestConfiguration.COOL_OPTIONS, Arrays.asList("Dinosaurs", "Explosions", "Big trucks"))
.build();
for (Map.Entry<Property<?>, Object> entry : expectedValues.entrySet()) {
assertThat("Property '" + entry.getKey().getPath() + "' has expected value",
settings.getProperty(entry.getKey()), equalTo(entry.getValue()));
}
}
private File getConfigFile(String file) {
URL url = getClass().getClassLoader().getResource(file);
if (url == null) {
throw new IllegalStateException("File '" + file + "' could not be loaded");
}
return new File(url.getFile());
}
}

View File

@ -0,0 +1,84 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Property;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.File;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link NewSetting}.
*/
public class NewSettingTest {
@Test
public void shouldLoadAllConfigs() {
// given
YamlConfiguration file = mock(YamlConfiguration.class);
given(file.getString(anyString(), anyString())).willAnswer(withDefaultArgument());
given(file.getBoolean(anyString(), anyBoolean())).willAnswer(withDefaultArgument());
given(file.getDouble(anyString(), anyDouble())).willAnswer(withDefaultArgument());
given(file.getInt(anyString(), anyInt())).willAnswer(withDefaultArgument());
setReturnValue(file, TestConfiguration.VERSION_NUMBER, 20);
setReturnValue(file, TestConfiguration.SKIP_BORING_FEATURES, true);
setReturnValue(file, TestConfiguration.RATIO_LIMIT, 4.25);
setReturnValue(file, TestConfiguration.SYSTEM_NAME, "myTestSys");
// when / then
NewSetting settings = new NewSetting(file, new File("conf.txt"), null);
assertThat(settings.getProperty(TestConfiguration.VERSION_NUMBER), equalTo(20));
assertThat(settings.getProperty(TestConfiguration.SKIP_BORING_FEATURES), equalTo(true));
assertThat(settings.getProperty(TestConfiguration.RATIO_LIMIT), equalTo(4.25));
assertThat(settings.getProperty(TestConfiguration.SYSTEM_NAME), equalTo("myTestSys"));
assertDefaultValue(TestConfiguration.DURATION_IN_SECONDS, settings);
assertDefaultValue(TestConfiguration.DUST_LEVEL, settings);
assertDefaultValue(TestConfiguration.COOL_OPTIONS, settings);
}
private static <T> void setReturnValue(YamlConfiguration config, Property<T> property, T value) {
if (value instanceof String) {
when(config.getString(eq(property.getPath()), anyString())).thenReturn((String) value);
} else if (value instanceof Integer) {
when(config.getInt(eq(property.getPath()), anyInt())).thenReturn((Integer) value);
} else if (value instanceof Boolean) {
when(config.getBoolean(eq(property.getPath()), anyBoolean())).thenReturn((Boolean) value);
} else if (value instanceof Double) {
when(config.getDouble(eq(property.getPath()), anyDouble())).thenReturn((Double) value);
} else {
throw new UnsupportedOperationException("Value has unsupported type '"
+ (value == null ? "null" : value.getClass().getSimpleName()) + "'");
}
}
private static void assertDefaultValue(Property<?> property, NewSetting setting) {
assertThat(property.getPath() + " has default value",
setting.getProperty(property).equals(property.getDefaultValue()), equalTo(true));
}
private static <T> Answer<T> withDefaultArgument() {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (T) invocation.getArguments()[1];
}
};
}
}

View File

@ -0,0 +1,119 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Test for {@link SettingsClass} implementations.
*/
public class SettingsClassConsistencyTest {
private static final String SETTINGS_FOLDER = "src/main/java/fr/xephi/authme/settings/custom";
private static List<Class<? extends SettingsClass>> classes;
@BeforeClass
public static void scanForSettingsClasses() {
File settingsFolder = new File(SETTINGS_FOLDER);
File[] filesInFolder = settingsFolder.listFiles();
if (filesInFolder == null || filesInFolder.length == 0) {
throw new IllegalStateException("Could not read folder '" + SETTINGS_FOLDER + "'. Is it correct?");
}
classes = new ArrayList<>();
for (File file : filesInFolder) {
Class<? extends SettingsClass> clazz = getSettingsClassFromFile(file);
if (clazz != null) {
classes.add(clazz);
}
}
System.out.println("Found " + classes.size() + " SettingsClass implementations");
}
/**
* Make sure that all {@link Property} instances we define are in public, static, final fields.
*/
@Test
public void shouldHavePublicStaticFinalFields() {
for (Class<?> clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (Property.class.isAssignableFrom(field.getType())) {
String fieldName = "Field " + clazz.getSimpleName() + "#" + field.getName();
assertThat(fieldName + " should be public, static, and final",
isValidConstantField(field), equalTo(true));
}
}
}
}
/**
* Make sure that no properties use the same path.
*/
@Test
public void shouldHaveUniquePaths() {
Set<String> paths = new HashSet<>();
for (Class<?> clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (Property.class.isAssignableFrom(field.getType())) {
Property<?> property =
(Property<?>) ReflectionTestUtils.getFieldValue(clazz, null, field.getName());
if (paths.contains(property.getPath())) {
fail("Path '" + property.getPath() + "' should be used by only one constant");
}
paths.add(property.getPath());
}
}
}
}
@Test
public void shouldHaveHiddenDefaultConstructorOnly() {
for (Class<?> clazz : classes) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
assertThat(clazz + " should only have one constructor",
constructors, arrayWithSize(1));
assertThat("Constructor of " + clazz + " is private",
Modifier.isPrivate(constructors[0].getModifiers()), equalTo(true));
}
}
private static boolean isValidConstantField(Field field) {
int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
}
private static Class<? extends SettingsClass> getSettingsClassFromFile(File file) {
String fileName = file.getPath();
String className = fileName
.substring("src/main/java/".length(), fileName.length() - ".java".length())
.replace(File.separator, ".");
try {
Class<?> clazz = SettingsClassConsistencyTest.class.getClassLoader().loadClass(className);
if (SettingsClass.class.isAssignableFrom(clazz)) {
return (Class<? extends SettingsClass>) clazz;
}
return null;
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Could not load class '" + className + "'", e);
}
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.PropertyType;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newProperty;
/**
* Sample properties for testing purposes.
*/
class TestConfiguration implements SettingsClass {
public static final Property<Integer> DURATION_IN_SECONDS =
newProperty("test.duration", 4);
public static final Property<String> SYSTEM_NAME =
newProperty("test.systemName", "[TestDefaultValue]");
public static final Property<Double> RATIO_LIMIT =
newProperty(PropertyType.DOUBLE, "sample.ratio.limit", 3.0);
public static final Property<List<String>> RATIO_FIELDS =
newProperty(PropertyType.STRING_LIST, "sample.ratio.fields", "a", "b", "c");
public static final Property<Integer> VERSION_NUMBER =
newProperty("version", 32046);
public static final Property<Boolean> SKIP_BORING_FEATURES =
newProperty("features.boring.skip", false);
public static final Property<List<String>> BORING_COLORS =
newProperty(PropertyType.STRING_LIST, "features.boring.colors");
public static final Property<Double> DUST_LEVEL =
newProperty(PropertyType.DOUBLE, "features.boring.dustLevel", 0.2);
public static final Property<Boolean> USE_COOL_FEATURES =
newProperty("features.cool.enabled", false);
public static final Property<List<String>> COOL_OPTIONS =
newProperty(PropertyType.STRING_LIST, "features.cool.options", "Sparks", "Sprinkles");
}

View File

@ -0,0 +1,118 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Test for {@link EnumPropertyType}.
*/
public class EnumPropertyTypeTest {
@Test
public void shouldReturnCorrectEnumValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn("Entry_B");
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_B));
}
@Test
public void shouldFallBackToDefaultForInvalidValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn("Bogus");
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_C));
}
@Test
public void shouldFallBackToDefaultForNonExistentValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "enum.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.getString(property.getPath())).willReturn(null);
// when
TestEnum result = propertyType.getFromFile(property, configuration);
// then
assertThat(result, equalTo(TestEnum.ENTRY_C));
}
@Test
public void shouldReturnTrueForContainsCheck() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(true);
given(configuration.getString(property.getPath())).willReturn("ENTRY_B");
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(true));
}
@Test
public void shouldReturnFalseForFileWithoutConfig() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(false);
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(false));
}
@Test
public void shouldReturnFalseForUnknownValue() {
// given
PropertyType<TestEnum> propertyType = new EnumPropertyType<>(TestEnum.class);
Property<TestEnum> property = Property.newProperty(TestEnum.class, "my.test.path", TestEnum.ENTRY_C);
YamlConfiguration configuration = mock(YamlConfiguration.class);
given(configuration.contains(property.getPath())).willReturn(true);
given(configuration.getString(property.getPath())).willReturn("wrong value");
// when
boolean result = propertyType.contains(property, configuration);
// then
assertThat(result, equalTo(false));
}
private enum TestEnum {
ENTRY_A,
ENTRY_B,
ENTRY_C
}
}

View File

@ -0,0 +1,182 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PropertyType} and the contained subtypes.
*/
public class PropertyTypeTest {
private static YamlConfiguration configuration;
@BeforeClass
public static void setUpYamlConfigurationMock() {
configuration = mock(YamlConfiguration.class);
when(configuration.getBoolean(eq("bool.path.test"), anyBoolean())).thenReturn(true);
when(configuration.getBoolean(eq("bool.path.wrong"), anyBoolean())).thenAnswer(secondParameter());
when(configuration.getDouble(eq("double.path.test"), anyDouble())).thenReturn(-6.4);
when(configuration.getDouble(eq("double.path.wrong"), anyDouble())).thenAnswer(secondParameter());
when(configuration.getInt(eq("int.path.test"), anyInt())).thenReturn(27);
when(configuration.getInt(eq("int.path.wrong"), anyInt())).thenAnswer(secondParameter());
when(configuration.getString(eq("str.path.test"), anyString())).thenReturn("Test value");
when(configuration.getString(eq("str.path.wrong"), anyString())).thenAnswer(secondParameter());
when(configuration.isList("list.path.test")).thenReturn(true);
when(configuration.getStringList("list.path.test")).thenReturn(Arrays.asList("test1", "Test2", "3rd test"));
when(configuration.isList("list.path.wrong")).thenReturn(false);
}
/* Boolean */
@Test
public void shouldGetBoolValue() {
// given
Property<Boolean> property = Property.newProperty("bool.path.test", false);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
@Test
public void shouldGetBoolDefault() {
// given
Property<Boolean> property = Property.newProperty("bool.path.wrong", true);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
/* Double */
@Test
public void shouldGetDoubleValue() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.test", 3.8);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-6.4));
}
@Test
public void shouldGetDoubleDefault() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.wrong", 12.0);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(12.0));
}
/* Integer */
@Test
public void shouldGetIntValue() {
// given
Property<Integer> property = Property.newProperty("int.path.test", 3);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(27));
}
@Test
public void shouldGetIntDefault() {
// given
Property<Integer> property = Property.newProperty("int.path.wrong", -10);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-10));
}
/* String */
@Test
public void shouldGetStringValue() {
// given
Property<String> property = Property.newProperty("str.path.test", "unused default");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("Test value"));
}
@Test
public void shouldGetStringDefault() {
// given
Property<String> property = Property.newProperty("str.path.wrong", "given default value");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("given default value"));
}
/* String list */
@Test
public void shouldGetStringListValue() {
// given
Property<List<String>> property = Property.newProperty(PropertyType.STRING_LIST, "list.path.test", "1", "b");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("test1", "Test2", "3rd test"));
}
@Test
public void shouldGetStringListDefault() {
// given
Property<List<String>> property =
Property.newProperty(PropertyType.STRING_LIST, "list.path.wrong", "default", "list", "elements");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("default", "list", "elements"));
}
private static <T> Answer<T> secondParameter() {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (T) invocation.getArguments()[1];
}
};
}
}

View File

@ -0,0 +1,53 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.contains;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PropertyMap}.
*/
public class PropertyMapTest {
@Test
public void shouldKeepEntriesByInsertionAndGroup() {
// given
List<String> paths = Arrays.asList("japan", "indonesia.jakarta", "japan.tokyo", "china.shanghai", "egypt.cairo",
"china.shenzhen", "china", "indonesia.jakarta.tugu", "egypt", "japan.nagoya", "japan.tokyo.taito");
PropertyMap map = new PropertyMap();
// when
for (String path : paths) {
Property<?> property = createPropertyWithPath(path);
map.put(property, new String[0]);
}
// then
Set<Map.Entry<Property<?>, String[]>> entrySet = map.entrySet();
List<String> resultPaths = new ArrayList<>(entrySet.size());
for (Map.Entry<Property<?>, String[]> entry : entrySet) {
resultPaths.add(entry.getKey().getPath());
}
Assert.assertThat(resultPaths, contains("japan", "japan.tokyo", "japan.tokyo.taito", "japan.nagoya",
"indonesia.jakarta", "indonesia.jakarta.tugu", "china", "china.shanghai", "china.shenzhen",
"egypt", "egypt.cairo"));
}
private static Property<?> createPropertyWithPath(String path) {
Property<?> property = mock(Property.class);
when(property.getPath()).thenReturn(path);
return property;
}
}

View File

@ -0,0 +1,27 @@
# Test config file with missing options from TestConfiguration
# Notice the commented out lines!
test:
duration: 22
# systemName: 'Custom sys name'
sample:
ratio:
# limit: 3.0
fields:
- 'Australia'
- 'Burundi'
- 'Colombia'
#version: 2492
features:
# boring:
# skip: false
# colors:
# - 'beige'
# - 'gray'
# dustLevel: 0.81
cool:
# enabled: true
options:
- 'Dinosaurs'
- 'Explosions'
- 'Big trucks'

View File

@ -0,0 +1,27 @@
# Test config file with all options
# defined in the TestConfiguration class
test:
duration: 22
systemName: 'Custom sys name'
sample:
ratio:
limit: -4.1
fields:
- 'Australia'
- 'Burundi'
- 'Colombia'
version: 2492
features:
boring:
skip: false
colors:
- 'beige'
- 'gray'
dustLevel: 0.81
cool:
enabled: true
options:
- 'Dinosaurs'
- 'Explosions'
- 'Big trucks'