Settings: use class constants for properties, create custom writer

- Create Property class for defining config properties
  - Create logic for typed retrival of properties from YAML file
- Add custom save method
  - Retain comments from Comment annotations in the classes
  - Write in a sorted order: first discovered properties are first written to config.yml
- Adjust properties to reflect the current config.yml
- Add sample tests for the retrieval and writing of properties with the new setup
This commit is contained in:
ljacqu 2016-01-03 15:22:32 +01:00
parent c2deb9d0b5
commit 7d41ccbc9c
23 changed files with 1225 additions and 518 deletions

View File

@ -1,46 +1,32 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING;
public class ConverterSettings extends CustomSetting {
public class ConverterSettings implements SettingsClass {
@Comment("Rakamak file name")
@Type(SettingType.String)
public String rakamakFileName = "users.rak";
public static final Property<String> RAKAMAK_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.fileName", "users.rak");
@Comment("Rakamak use Ip ?")
@Type(SettingType.Boolean)
public boolean rakamakeUseIP = false;
@Comment("Rakamak use IP?")
public static final Property<Boolean> RAKAMAK_USE_IP =
newProperty(BOOLEAN, "Converter.Rakamak.useIP", false);
@Comment("Rakamak IP file name")
@Type(SettingType.String)
public String rakamakIPFileName = "UsersIp.rak";
public static final Property<String> RAKAMAK_IP_FILE_NAME =
newProperty(STRING, "Converter.Rakamak.ipFileName", "UsersIp.rak");
@Comment("CrazyLogin database file name")
@Type(SettingType.String)
public String crazyLoginFileName = "accounts.db";
public static final Property<String> CRAZYLOGIN_FILE_NAME =
newProperty(STRING, "Converter.CrazyLogin.fileName", "accounts.db");
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "converter.yml");
private ConverterSettings() {
}
private ConverterSettings instance;
public ConverterSettings()
{
super(configFile);
instance = this;
}
public ConverterSettings getInstance() {
return instance;
}
public void setInstance(ConverterSettings instance) {
this.instance = instance;
}
}

View File

@ -1,156 +0,0 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.CustomConfiguration;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
public class CustomSetting extends CustomConfiguration {
private File configFile;
public boolean isFirstLaunch = false;
public CustomSetting(File file) {
super(file);
this.configFile = file;
try {
if (!configFile.exists())
{
isFirstLaunch = true;
configFile.createNewFile();
}
else
{
load();
loadValues();
}
save();
} catch (IOException e)
{
ConsoleLogger.writeStackTrace(e);
}
}
@Override
public boolean reLoad()
{
boolean out = true;
if (!configFile.exists()) {
try {
configFile.createNewFile();
save();
} catch (IOException e) {
out = false;
}
}
if (out)
{
load();
loadValues();
}
return out;
}
public void loadValues()
{
for (Field f : this.getClass().getDeclaredFields())
{
if (!f.isAnnotationPresent(Type.class))
continue;
f.setAccessible(true);
try {
switch (f.getAnnotation(Type.class).value())
{
case Boolean:
f.setBoolean(this, this.getBoolean(f.getName()));
break;
case Double:
f.setDouble(this, this.getDouble(f.getName()));
break;
case Int:
f.setInt(this, this.getInt(f.getName()));
break;
case Long:
f.setLong(this, this.getLong(f.getName()));
break;
case String:
f.set(this, this.getString(f.getName()));
break;
case StringList:
f.set(this, this.getStringList(f.getName()));
break;
default:
break;
}
} catch (Exception e)
{
ConsoleLogger.writeStackTrace(e);
}
}
}
@Override
public void save()
{
FileWriter writer = null;
try {
writer = new FileWriter(configFile);
writer.write("");
for (Field f : this.getClass().getDeclaredFields())
{
if (!f.isAnnotationPresent(Comment.class))
continue;
if (!f.isAnnotationPresent(Type.class))
continue;
for (String s : f.getAnnotation(Comment.class).value())
{
writer.append("# " + s + "\n");
}
writer.append(f.getName() + ": ");
switch (f.getAnnotation(Type.class).value())
{
case Boolean:
writer.append(f.getBoolean(this) ? "true" : "false");
break;
case Double:
writer.append("" + f.getDouble(this));
break;
case Int:
writer.append("" + f.getInt(this));
break;
case String:
writer.append("'" + f.get(this).toString() + "'");
break;
case StringList:
@SuppressWarnings("unchecked")
List<String> list = (List<String>) f.get(this);
writer.append("\n");
if (list.isEmpty())
writer.write("[]");
else
for (String s : list)
writer.append(" - '" + s + "'\n");
break;
case Long:
writer.append("" + f.getLong(this));
break;
default:
break;
}
writer.append("\n");
writer.flush();
}
writer.close();
} catch (Exception e) {
ConsoleLogger.writeStackTrace(e);
}
}
}

View File

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

View File

@ -1,83 +1,72 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import java.util.List;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.custom.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING;
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING_LIST;
public class EmailSettings extends CustomSetting {
public class EmailSettings implements SettingsClass {
@Comment("Email SMTP server host")
@Type(SettingType.String)
public String mailSMTP = "smtp.gmail.com";
public static final Property<String> SMTP_HOST =
newProperty(STRING, "Email.mailSMTP", "smtp.gmail.com");
@Comment("Email SMTP server port")
@Type(SettingType.Int)
public int mailPort = 465;
public static final Property<Integer> SMTP_PORT =
newProperty(INTEGER, "Email.mailPort", 465);
@Comment("Email account whose send the mail")
@Type(SettingType.String)
public String mailAccount = "";
@Comment("Email account which sends the mails")
public static final Property<String> MAIL_ACCOUNT =
newProperty(STRING, "Email.mailAccount", "");
@Comment("Email account password")
@Type(SettingType.String)
public String mailPassword = "";
public static final Property<String> MAIL_PASSWORD =
newProperty(STRING, "Email.mailPassword", "");
@Comment("Random password length")
@Type(SettingType.Int)
public int recoveryPasswordLength = 8;
@Comment("Recovery password length")
public static final Property<Integer> RECOVERY_PASSWORD_LENGTH =
newProperty(INTEGER, "Email.RecoveryPasswordLength", 8);
@Comment("Mail Subject")
@Type(SettingType.String)
public String mailSubject = "Your new AuthMe password";
public static final Property<String> RECOVERY_MAIL_SUBJECT =
newProperty(STRING, "Email.mailSubject", "Your new AuthMe password");
@Comment("Like maxRegPerIP but with email")
@Type(SettingType.Int)
public int maxRegPerEmail = 1;
public static final Property<Integer> MAX_REG_PER_EMAIL =
newProperty(INTEGER, "Email.maxRegPerEmail", 1);
@Comment("Recall players to add an email ?")
@Type(SettingType.Boolean)
public boolean recallPlayers = false;
@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")
@Type(SettingType.Int)
public int delayRecall = 5;
public static final Property<Integer> DELAY_RECALL =
newProperty(INTEGER, "Email.delayRecall", 5);
@Comment("Blacklist these domains for emails")
@Type(SettingType.StringList)
public List<String> emailBlackListed = new ArrayList<String>();
public static final Property<List<String>> DOMAIN_BLACKLIST =
newProperty(STRING_LIST, "Email.emailBlacklisted", "10minutemail.com");
@Comment("Whitelist ONLY these domains for emails")
@Type(SettingType.StringList)
public List<String> emailWhiteListed = new ArrayList<String>();
public static final Property<List<String>> DOMAIN_WHITELIST =
newProperty(STRING_LIST, "Email.emailWhitelisted");
@Comment("Do we need to send new password draw in an image ?")
@Type(SettingType.Boolean)
public boolean generateImage = false;
@Comment("Send the new password drawn in an image?")
public static final Property<Boolean> PASSWORD_AS_IMAGE =
newProperty(BOOLEAN, "Email.generateImage", false);
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "emails.yml");
@Comment("The OAuth2 token")
public static final Property<String> OAUTH2_TOKEN =
newProperty(STRING, "Email.emailOauth2Token", "");
private EmailSettings instance;
private EmailSettings() {
}
public EmailSettings()
{
super(configFile);
instance = this;
if (this.isFirstLaunch)
{
this.emailBlackListed.add("10minutemail.com");
save();
}
}
public EmailSettings getInstance() {
return instance;
}
public void setInstance(EmailSettings instance) {
this.instance = instance;
}
}

View File

@ -1,54 +1,39 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.PropertyType;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
public class HooksSettings extends CustomSetting {
public class HooksSettings implements SettingsClass {
@Comment("Do we need to hook with multiverse for spawn checking?")
@Type(SettingType.Boolean)
public boolean multiverse = true;
public static final Property<Boolean> MULTIVERSE =
newProperty(PropertyType.BOOLEAN, "Hooks.multiverse", true);
@Comment("Do we need to hook with BungeeCord ?")
@Type(SettingType.Boolean)
public boolean bungeecord = false;
@Comment("Do we need to hook with BungeeCord?")
public static final Property<Boolean> BUNGEECORD =
newProperty(PropertyType.BOOLEAN, "Hooks.bungeecord", false);
@Comment("Send player to this BungeeCord server after register/login")
@Type(SettingType.String)
public String sendPlayerTo = "";
public static final Property<String> BUNGEECORD_SERVER =
newProperty(PropertyType.STRING, "bungeecord.server", "");
@Comment("Do we need to disable Essentials SocialSpy on join?")
@Type(SettingType.Boolean)
public boolean disableSocialSpy = false;
public static final Property<Boolean> DISABLE_SOCIAL_SPY =
newProperty(PropertyType.BOOLEAN, "Hooks.disableSocialSpy", false);
@Comment("Do we need to force /motd Essentials command on join?")
@Type(SettingType.Boolean)
public boolean useEssentialsMotd = false;
public static final Property<Boolean> USE_ESSENTIALS_MOTD =
newProperty(PropertyType.BOOLEAN, "Hooks.useEssentialsMotd", false);
@Comment("Do we need to cache custom Attributes?")
@Type(SettingType.Boolean)
public boolean customAttributes = false;
public static final Property<Boolean> CACHE_CUSTOM_ATTRIBUTES =
newProperty(PropertyType.BOOLEAN, "Hooks.customAttributes", false);
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "hooks.yml");
private HooksSettings() {
}
private HooksSettings instance;
public HooksSettings()
{
super(configFile);
instance = this;
}
public HooksSettings getInstance() {
return instance;
}
public void setInstance(HooksSettings instance) {
this.instance = instance;
}
}

View File

@ -0,0 +1,148 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import fr.xephi.authme.settings.custom.propertymap.PropertyMap;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* The new settings manager.
*/
public class NewSetting {
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
ConverterSettings.class, DatabaseSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class);
private static final int YAML_INDENTATION = 4;
private File file;
private YamlConfiguration configuration;
public NewSetting(File file) {
this.configuration = YamlConfiguration.loadConfiguration(file);
this.file = file;
}
// TODO: No way of passing just a YamlConfiguration object (later on for mocking purposes) ?
// If not, best is probably to keep this constructor as package-private with @VisibleForTesting
// but it's not a satisfying solution
public NewSetting(YamlConfiguration yamlConfiguration, String file) {
this.configuration = yamlConfiguration;
this.file = new File(file);
}
public <T> T getOption(Property<T> property) {
return property.getFromFile(configuration);
}
public void save() {
PropertyMap properties = getAllPropertyFields();
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 : properties.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(StringUtils.repeat(" ", indentationLevel * YAML_INDENTATION))
.append(path)
.append(": ");
++indentationLevel;
}
}
for (String comment : entry.getValue()) {
writer.append("\n")
.append(StringUtils.repeat(" ", indentationLevel * YAML_INDENTATION))
.append("# ")
.append(comment);
}
writer.append("\n")
.append(StringUtils.repeat(" ", indentationLevel * YAML_INDENTATION))
.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" + StringUtils.repeat(" ", indentationLevel * YAML_INDENTATION);
}
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);
}
}
private 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

@ -1,63 +1,46 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import java.util.List;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.custom.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING_LIST;
public class ProtectionSettings extends CustomSetting {
@Comment("Enable some servers protection ( country based login, antibot )")
@Type(SettingType.Boolean)
public boolean enableProtection = false;
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 !"})
@Type(SettingType.StringList)
public List<String> countriesWhitelist = new ArrayList<String>();
"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 !"})
@Type(SettingType.StringList)
public List<String> countriesBlacklist = new ArrayList<String>();
"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?")
@Type(SettingType.Boolean)
public boolean enableAntiBot = false;
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")
@Type(SettingType.Int)
public int antiBotSensibility = 5;
public static final Property<Integer> ANTIBOT_SENSIBILITY =
newProperty(INTEGER, "Protection.antiBotSensibility", 5);
@Comment("Duration in minutes of the antibot automatic system")
@Type(SettingType.Int)
public int antiBotDuration = 10;
public static final Property<Integer> ANTIBOT_DURATION =
newProperty(INTEGER, "Protection.antiBotDuration", 10);
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "protection.yml");
private ProtectionSettings() {
}
private ProtectionSettings instance;
public ProtectionSettings()
{
super(configFile);
instance = this;
if (this.isFirstLaunch)
{
this.countriesWhitelist.add("US");
this.countriesWhitelist.add("GB");
this.countriesBlacklist.add("A1");
save();
}
}
public ProtectionSettings getInstance() {
return instance;
}
public void setInstance(ProtectionSettings instance) {
this.instance = instance;
}
}

View File

@ -1,62 +1,49 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.custom.domain.PropertyType.INTEGER;
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING;
public class PurgeSettings extends CustomSetting {
public class PurgeSettings implements SettingsClass {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
@Type(SettingType.Boolean)
public boolean useAutoPurge = false;
public static final Property<Boolean> USE_AUTO_PURGE =
newProperty(BOOLEAN, "Purge.useAutoPurge", false);
@Comment("Number of Days an account become Unused")
@Type(SettingType.Int)
public int daysBeforeRemovePlayer = 60;
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?")
@Type(SettingType.Boolean)
public boolean removePlayerDat = false;
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?")
@Type(SettingType.Boolean)
public boolean removeEssentialsFiles = false;
public static final Property<Boolean> REMOVE_ESSENTIALS_FILES =
newProperty(BOOLEAN, "Purge.removeEssentialsFiles", false);
@Comment("World where are players.dat stores")
@Type(SettingType.String)
public String defaultWorld = "world";
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 ?")
@Type(SettingType.Boolean)
public boolean removeLimiteCreativeInventories = false;
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?")
@Type(SettingType.Boolean)
public boolean removeAntiXRayFile = false;
public static final Property<Boolean> REMOVE_ANTI_XRAY_FILE =
newProperty(BOOLEAN, "Purge.removeAntiXRayFile", false);
@Comment("Do we need to remove permissions?")
@Type(SettingType.Boolean)
public boolean removePermissions = false;
public static final Property<Boolean> REMOVE_PERMISSIONS =
newProperty(BOOLEAN, "Purge.removePermissions", false);
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "purge.yml");
private PurgeSettings() {
}
private PurgeSettings instance;
public PurgeSettings()
{
super(configFile);
instance = this;
}
public PurgeSettings getInstance() {
return instance;
}
public void setInstance(PurgeSettings instance) {
this.instance = instance;
}
}

View File

@ -1,60 +1,51 @@
package fr.xephi.authme.settings.custom;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import fr.xephi.authme.settings.custom.domain.Comment;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.SettingsClass;
import fr.xephi.authme.settings.custom.annotations.Comment;
import fr.xephi.authme.settings.custom.annotations.Type;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
import static fr.xephi.authme.settings.custom.domain.PropertyType.INTEGER;
public class SecuritySettings extends CustomSetting {
public class SecuritySettings implements SettingsClass {
@Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set that to false,",
"AuthMe automatically disable and the server is not protected!"})
@Type(SettingType.Boolean)
public boolean stopServerOnProblem = true;
"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(BOOLEAN, "Security.SQLProblem.stopServer", true);
@Comment("/reload support")
@Type(SettingType.Boolean)
public boolean useReloadCommandSupport = true;
public static final Property<Boolean> USE_RELOAD_COMMAND_SUPPORT =
newProperty(BOOLEAN, "Security.ReloadCommand.useReloadCommandSupport", true);
@Comment("Remove Spam from Console ?")
@Type(SettingType.Boolean)
public boolean removeSpamFromConsole = false;
@Comment("Remove spam from console?")
public static final Property<Boolean> REMOVE_SPAM_FROM_CONSOLE =
newProperty(BOOLEAN, "Security.console.noConsoleSpam", false);
@Comment("Remove Password from Console ?")
@Type(SettingType.Boolean)
public boolean removePasswordFromConsole = true;
@Comment("Remove passwords from console?")
public static final Property<Boolean> REMOVE_PASSWORD_FROM_CONSOLE =
newProperty(BOOLEAN, "Security.console.removePassword", true);
@Comment("Player need to put a captcha when he fails too lot the password")
@Type(SettingType.Boolean)
public boolean useCaptcha = false;
public static final Property<Boolean> USE_CAPTCHA =
newProperty(BOOLEAN, "Security.captcha.useCaptcha", false);
@Comment("Max allowed tries before request a captcha")
@Type(SettingType.Int)
public int maxLoginTryBeforeCaptcha = 5;
public static final Property<Integer> MAX_LOGIN_TRIES_BEFORE_CAPTCHA =
newProperty(INTEGER, "Security.captcha.maxLoginTry", 5);
@Comment("Captcha length ")
@Type(SettingType.Int)
public int captchaLength = 5;
@Comment("Captcha length")
public static final Property<Integer> CAPTCHA_LENGTH =
newProperty(INTEGER, "Security.captcha.captchaLength", 5);
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "security.yml");
@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(BOOLEAN, "Security.stop.kickPlayersBeforeStopping", true);
private SecuritySettings instance;
private SecuritySettings() {
}
public SecuritySettings()
{
super(configFile);
instance = this;
}
public SecuritySettings getInstance() {
return instance;
}
public void setInstance(SecuritySettings instance) {
this.instance = instance;
}
}

View File

@ -1,43 +0,0 @@
package fr.xephi.authme.settings.custom.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
/**
*
* Set the type of a field value
*
* @author xephi59
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Type {
public enum SettingType {
String(0),
Int(1),
Boolean(2),
Double(3),
StringList(4),
Long(5);
private int type;
SettingType(int type)
{
this.type = type;
}
public int getType()
{
return this.type;
}
}
public SettingType value() default SettingType.String;
}

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.settings.custom.annotations;
package fr.xephi.authme.settings.custom.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -6,15 +6,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* Add a comment to a field value
*
* @author xephi59
*
* Comment for properties which are also included in the YAML file upon saving.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment {
public String[] value() default "";
String[] value() default "";
}

View File

@ -0,0 +1,100 @@
package fr.xephi.authme.settings.custom.domain;
import org.bukkit.configuration.file.YamlConfiguration;
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));
}
// -----
// 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(YamlConfiguration 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(YamlConfiguration configuration) {
return type.asYaml(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,146 @@
package fr.xephi.authme.settings.custom.domain;
import org.bukkit.configuration.file.YamlConfiguration;
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, YamlConfiguration 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, YamlConfiguration configuration) {
return asYaml(getFromFile(property, configuration));
}
/**
* 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, YamlConfiguration 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, YamlConfiguration 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, YamlConfiguration 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, YamlConfiguration 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, YamlConfiguration 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;
}
}
}

View File

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

View File

@ -0,0 +1,121 @@
package fr.xephi.authme.settings.custom.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,48 @@
package fr.xephi.authme.settings.custom.propertymap;
import fr.xephi.authme.settings.custom.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[]> propertyMap;
private PropertyMapComparator comparator;
/**
* Create a new property map.
*/
public PropertyMap() {
comparator = new PropertyMapComparator();
propertyMap = 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);
propertyMap.put(property, comments);
}
/**
* Return the entry set of the map.
*
* @return The entry set
*/
public Set<Map.Entry<Property, String[]>> entrySet() {
return propertyMap.entrySet();
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.settings.custom.propertymap;
import fr.xephi.authme.settings.custom.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,17 @@ 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> coll1, List<T> coll2) {
List<T> commonStart = new ArrayList<>();
int minSize = Math.min(coll1.size(), coll2.size());
for (int i = 0; i < minSize; ++i) {
if (Objects.equals(coll1.get(i), coll2.get(i))) {
commonStart.add(coll1.get(i));
} else {
break;
}
}
return commonStart;
}
}

View File

@ -117,4 +117,12 @@ public final class StringUtils {
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
}
public static String repeat(String str, int times) {
StringBuilder sb = new StringBuilder(str.length() * times);
for (int i = 0; i < times; ++i) {
sb.append(str);
}
return sb.toString();
}
}

View File

@ -0,0 +1,111 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.custom.domain.Property;
import fr.xephi.authme.settings.custom.domain.PropertyType;
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 java.net.URL;
import java.util.List;
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
public class NewSettingTest {
private static final String CONFIG_FILE = "437-config-test.yml";
@Test
public void shouldReturnIntegerFromFile() {
// given
YamlConfiguration file = mock(YamlConfiguration.class);
Property<Integer> config = TestConfiguration.DURATION_IN_SECONDS;
given(file.getInt("test.duration", 4)).willReturn(18);
NewSetting settings = new NewSetting(file, "conf.txt");
// when
int retrieve = settings.getOption(config);
// then
assertThat(retrieve, equalTo(18));
}
@Test
public void shouldLoadAllConfigs() {
// given
YamlConfiguration file = mock(YamlConfiguration.class);
given(file.getString(anyString(), anyString())).willAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (String) invocation.getArguments()[1];
}
});
given(file.getInt(eq(EmailSettings.RECOVERY_PASSWORD_LENGTH.getPath()), anyInt()))
.willReturn(20);
given(file.getBoolean(eq(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE.getPath()), anyBoolean()))
.willReturn(false);
// when
NewSetting settings = new NewSetting(file, "conf.txt");
// then
// Expect the value we told the YAML mock to return:
assertThat(settings.getOption(EmailSettings.RECOVERY_PASSWORD_LENGTH), equalTo(20));
// Expect the default:
assertThat(settings.getOption(EmailSettings.SMTP_HOST), equalTo(EmailSettings.SMTP_HOST.getDefaultValue()));
// Expect the value we told the YAML mock to return:
assertThat(settings.getOption(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE), equalTo(false));
}
@Test
public void executeIntegrationTest() {
// given
YamlConfiguration yamlFile = YamlConfiguration.loadConfiguration(getConfigFile());
NewSetting settings = new NewSetting(yamlFile, "conf.txt");
// when
int result = settings.getOption(TestConfiguration.DURATION_IN_SECONDS);
String systemName = settings.getOption(TestConfiguration.SYSTEM_NAME);
String helpHeader = settings.getOption(newProperty("settings.helpHeader", ""));
List<String> unsafePasswords = settings.getOption(
newProperty(PropertyType.STRING_LIST, "Security.unsafePasswords"));
// then
assertThat(result, equalTo(22));
assertThat(systemName, equalTo(TestConfiguration.SYSTEM_NAME.getDefaultValue()));
assertThat(helpHeader, equalTo("AuthMeReloaded"));
assertThat(unsafePasswords, contains("123456", "qwerty", "54321"));
}
private File getConfigFile() {
URL url = getClass().getClassLoader().getResource(CONFIG_FILE);
if (url == null) {
throw new RuntimeException("File '" + CONFIG_FILE + "' could not be loaded");
}
return new File(url.getFile());
}
private static class TestConfiguration {
public static final Property<Integer> DURATION_IN_SECONDS =
newProperty("test.duration", 4);
public static final Property<String> SYSTEM_NAME =
newProperty("test.systemName", "[TestDefaultValue]");
}
}

View File

@ -0,0 +1,42 @@
package fr.xephi.authme.settings.custom;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.equalTo;
/**
* Test for the save() function of new settings
*/
public class NewSettingsWriteTest {
private static final String CONFIG_FILE = "437-write-test.yml";
@Test
public void shouldWriteProperties() {
File file = getConfigFile();
NewSetting setting = new NewSetting(file);
setting.save();
// assert that we can load the file again -- i.e. that it's valid YAML!
NewSetting newSetting = new NewSetting(file);
assertThat(newSetting.getOption(SecuritySettings.CAPTCHA_LENGTH),
equalTo(SecuritySettings.CAPTCHA_LENGTH.getDefaultValue()));
assertThat(newSetting.getOption(ProtectionSettings.COUNTRIES_BLACKLIST),
equalTo(ProtectionSettings.COUNTRIES_BLACKLIST.getDefaultValue()));
}
private File getConfigFile() {
URL url = getClass().getClassLoader().getResource(CONFIG_FILE);
if (url == null) {
throw new RuntimeException("File '" + CONFIG_FILE + "' could not be loaded");
}
return new File(url.getFile());
}
}

View File

@ -0,0 +1,44 @@
test:
duration: 22
DataSource:
# What type of database do you want to use?
# Valid values: sqlite, mysql
backend: sqlite
# Enable database caching, should improve database performance
caching: true
settings:
# The name shown in the help messages.
helpHeader: AuthMeReloaded
GameMode:
# ForceSurvivalMode to player when join ?
ForceSurvivalMode: false
Security:
SQLProblem:
# Stop the server if we can't contact the sql database
# Take care with this, if you set that to false,
# AuthMe automatically disable and the server is not protected!
stopServer: true
ReloadCommand:
# /reload support
useReloadCommandSupport: true
console:
# Remove spam console
noConsoleSpam: false
# Replace passwords in the console when player type a command like /login
removePassword: true
captcha:
# Player need to put a captcha when he fails too lot the password
useCaptcha: false
# Max allowed tries before request a captcha
maxLoginTry: 5
# Captcha length
captchaLength: 5
unsafePasswords:
- '123456'
- 'qwerty'
- '54321'
Email:
# Email SMTP server host
mailSMTP: smtp.gmail.com
# Email SMTP server port
mailPort: 465

View File

@ -0,0 +1,172 @@
Converter:
Rakamak:
# Rakamak file name
fileName: 'users.rak'
# Rakamak use IP?
useIP: false
# Rakamak IP file name
ipFileName: 'UsersIp.rak'
CrazyLogin:
# CrazyLogin database file name
fileName: 'accounts.db'
DataSource:
# What type of database do you want to use?
# Valid values: sqlite, mysql
backend: 'sqlite'
# Enable database caching, should improve database performance
caching: true
# Database host address
mySQLHost: '127.0.0.1'
# Database port
mySQLPort: '3306'
# Username about Database Connection Infos
mySQLUsername: 'authme'
# Password about Database Connection Infos
mySQLPassword: '123456'
# Database Name, use with converters or as SQLITE database name
mySQLDatabase: 'authme'
# Table of the database
mySQLTablename: 'authme'
# Column of IDs to sort data
mySQLColumnId: 'id'
# Column for storing or checking players nickname
mySQLColumnName: 'username'
# Column for storing or checking players RealName
mySQLRealName: 'realname'
# Column for storing players passwords
mySQLColumnPassword: 'password'
# Column for storing players emails
mySQLColumnEmail: 'email'
# Column for storing if a player is logged in or not
mySQLColumnLogged: 'email'
# Column for storing players ips
mySQLColumnIp: 'ip'
# Column for storing players lastlogins
mySQLColumnLastLogin: 'lastlogin'
# Column for storing player LastLocation - X
mySQLlastlocX: 'x'
# Column for storing player LastLocation - Y
mySQLlastlocY: 'y'
# Column for storing player LastLocation - Z
mySQLlastlocZ: 'z'
# Column for storing player LastLocation - World Name
mySQLlastlocWorld: 'world'
# Enable this when you allow registration through a website
mySQLWebsite: false
ExternalBoardOptions:
# Column for storing players passwords salts
mySQLColumnSalt: ''
# Column for storing players groups
mySQLColumnGroup: ''
Email:
# Email SMTP server host
mailSMTP: 'smtp.gmail.com'
# Email SMTP server port
mailPort: 465
# Email account which sends the mails
mailAccount: ''
# Email account password
mailPassword: ''
# Recovery password length
RecoveryPasswordLength: 8
# Mail Subject
mailSubject: 'Your new AuthMe password'
# Like maxRegPerIP but with email
maxRegPerEmail: 1
# Recall players to add an email?
recallPlayers: false
# Delay in minute for the recall scheduler
delayRecall: 5
# Blacklist these domains for emails
emailBlacklisted:
- '10minutemail.com'
# Whitelist ONLY these domains for emails
emailWhitelisted: []
# Send the new password drawn in an image?
generateImage: false
# The OAuth2 token
emailOauth2Token: ''
Hooks:
# Do we need to hook with multiverse for spawn checking?
multiverse: true
# Do we need to hook with BungeeCord?
bungeecord: false
# Do we need to disable Essentials SocialSpy on join?
disableSocialSpy: false
# Do we need to force /motd Essentials command on join?
useEssentialsMotd: false
# Do we need to cache custom Attributes?
customAttributes: false
bungeecord:
# Send player to this BungeeCord server after register/login
server: ''
Protection:
# Enable some servers protection (country based login, antibot)
enableProtection: false
# 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!
countries:
- 'US'
- 'GB'
- 'A1'
# Countries not allowed to join the server and register
# PLEASE USE QUOTES!
countriesBlacklist: []
# Do we need to enable automatic antibot system?
enableAntiBot: false
# Max number of player allowed to login in 5 secs before enable AntiBot system automatically
antiBotSensibility: 5
# Duration in minutes of the antibot automatic system
antiBotDuration: 10
Purge:
# If enabled, AuthMe automatically purges old, unused accounts
useAutoPurge: false
# Number of Days an account become Unused
daysBeforeRemovePlayer: 60
# Do we need to remove the player.dat file during purge process?
removePlayerDat: false
# Do we need to remove the Essentials/users/player.yml file during purge process?
removeEssentialsFiles: false
# World where are players.dat stores
defaultWorld: 'world'
# Do we need to remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge process ?
removeLimitedCreativesInventories: false
# Do we need to remove the AntiXRayData/PlayerData/player file during purge process?
removeAntiXRayFile: false
# Do we need to remove permissions?
removePermissions: false
Security:
SQLProblem:
# 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!
stopServer: true
ReloadCommand:
# /reload support
useReloadCommandSupport: true
console:
# Remove spam from console?
noConsoleSpam: false
# Remove passwords from console?
removePassword: true
captcha:
# Player need to put a captcha when he fails too lot the password
useCaptcha: false
# Max allowed tries before request a captcha
maxLoginTry: 5
# Captcha length
captchaLength: 5
stop:
# Kick players before stopping the server, that allow us to save position of players
# and all needed information correctly without any corruption.
kickPlayersBeforeStopping: true