mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2025-01-04 15:07:49 +01:00
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:
parent
c2deb9d0b5
commit
7d41ccbc9c
@ -1,46 +1,32 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
import java.util.List;
|
import fr.xephi.authme.settings.custom.domain.SettingsClass;
|
||||||
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Comment;
|
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.STRING;
|
||||||
|
|
||||||
public class ConverterSettings extends CustomSetting {
|
public class ConverterSettings implements SettingsClass {
|
||||||
|
|
||||||
@Comment("Rakamak file name")
|
@Comment("Rakamak file name")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> RAKAMAK_FILE_NAME =
|
||||||
public String rakamakFileName = "users.rak";
|
newProperty(STRING, "Converter.Rakamak.fileName", "users.rak");
|
||||||
|
|
||||||
@Comment("Rakamak use Ip ?")
|
@Comment("Rakamak use IP?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> RAKAMAK_USE_IP =
|
||||||
public boolean rakamakeUseIP = false;
|
newProperty(BOOLEAN, "Converter.Rakamak.useIP", false);
|
||||||
|
|
||||||
@Comment("Rakamak IP file name")
|
@Comment("Rakamak IP file name")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> RAKAMAK_IP_FILE_NAME =
|
||||||
public String rakamakIPFileName = "UsersIp.rak";
|
newProperty(STRING, "Converter.Rakamak.ipFileName", "UsersIp.rak");
|
||||||
|
|
||||||
@Comment("CrazyLogin database file name")
|
@Comment("CrazyLogin database file name")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> CRAZYLOGIN_FILE_NAME =
|
||||||
public String crazyLoginFileName = "accounts.db";
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,121 +1,109 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
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 static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
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?",
|
@Comment({"What type of database do you want to use?",
|
||||||
"Valid values: sqlite, mysql"})
|
"Valid values: sqlite, mysql"})
|
||||||
@Type(SettingType.String)
|
public static final Property<String> BACKEND =
|
||||||
public String backend = "sqlite";
|
newProperty(STRING, "DataSource.backend", "sqlite");
|
||||||
|
|
||||||
@Comment("Enable database caching, should improve database performance")
|
@Comment("Enable database caching, should improve database performance")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> USE_CACHING =
|
||||||
public boolean caching = true;
|
newProperty(BOOLEAN, "DataSource.caching", true);
|
||||||
|
|
||||||
@Comment("Database host address")
|
@Comment("Database host address")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_HOST =
|
||||||
public String mySQLHost = "127.0.0.1";
|
newProperty(STRING, "DataSource.mySQLHost", "127.0.0.1");
|
||||||
|
|
||||||
@Comment("Database port")
|
@Comment("Database port")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_PORT =
|
||||||
public String mySQLPort = "3306";
|
newProperty(STRING, "DataSource.mySQLPort", "3306");
|
||||||
|
|
||||||
@Comment("Username about Database Connection Infos")
|
@Comment("Username about Database Connection Infos")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_USERNAME =
|
||||||
public String mySQLUsername = "authme";
|
newProperty(STRING, "DataSource.mySQLUsername", "authme");
|
||||||
|
|
||||||
@Comment("Password about Database Connection Infos")
|
@Comment("Password about Database Connection Infos")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_PASSWORD =
|
||||||
public String mySQLPassword = "12345";
|
newProperty(STRING, "DataSource.mySQLPassword", "123456");
|
||||||
|
|
||||||
@Comment("Database Name, use with converters or as SQLITE database name")
|
@Comment("Database Name, use with converters or as SQLITE database name")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_DATABASE =
|
||||||
public String mySQLDatabase = "authme";
|
newProperty(STRING, "DataSource.mySQLDatabase", "authme");
|
||||||
|
|
||||||
@Comment("Table of the database")
|
@Comment("Table of the database")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_TABLE =
|
||||||
public String mySQLTablename = "authme";
|
newProperty(STRING, "DataSource.mySQLTablename", "authme");
|
||||||
|
|
||||||
@Comment("Column of IDs to sort data")
|
@Comment("Column of IDs to sort data")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_ID =
|
||||||
public String mySQLColumnId = "id";
|
newProperty(STRING, "DataSource.mySQLColumnId", "id");
|
||||||
|
|
||||||
@Comment("Column for storing or checking players nickname")
|
@Comment("Column for storing or checking players nickname")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_NAME =
|
||||||
public String mySQLColumnName = "username";
|
newProperty(STRING, "DataSource.mySQLColumnName", "username");
|
||||||
|
|
||||||
@Comment("Column for storing or checking players RealName ")
|
@Comment("Column for storing or checking players RealName ")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_REALNAME =
|
||||||
public String mySQLColumnRealName = "realname";
|
newProperty(STRING, "DataSource.mySQLRealName", "realname");
|
||||||
|
|
||||||
@Comment("Column for storing players passwords")
|
@Comment("Column for storing players passwords")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_PASSWORD =
|
||||||
public String mySQLColumnPassword = "password";
|
newProperty(STRING, "DataSource.mySQLColumnPassword", "password");
|
||||||
|
|
||||||
@Comment("Column for storing players passwords salts")
|
@Comment("Column for storing players passwords salts")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_SALT =
|
||||||
public String mySQLColumnSalt = "";
|
newProperty(STRING, "ExternalBoardOptions.mySQLColumnSalt", "");
|
||||||
|
|
||||||
@Comment("Column for storing players emails")
|
@Comment("Column for storing players emails")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_EMAIL =
|
||||||
public String mySQLColumnEmail = "email";
|
newProperty(STRING, "DataSource.mySQLColumnEmail", "email");
|
||||||
|
|
||||||
@Comment("Column for storing if a player is logged in or not")
|
@Comment("Column for storing if a player is logged in or not")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_ISLOGGED =
|
||||||
public String mySQLColumnLogged = "isLogged";
|
newProperty(STRING, "DataSource.mySQLColumnLogged", "isLogged");
|
||||||
|
|
||||||
@Comment("Column for storing players ips")
|
@Comment("Column for storing players ips")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_IP =
|
||||||
public String mySQLColumnIp = "ip";
|
newProperty(STRING, "DataSource.mySQLColumnIp", "ip");
|
||||||
|
|
||||||
@Comment("Column for storing players lastlogins")
|
@Comment("Column for storing players lastlogins")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_LASTLOGIN =
|
||||||
public String mySQLColumnLastLogin = "lastlogin";
|
newProperty(STRING, "DataSource.mySQLColumnLastLogin", "lastlogin");
|
||||||
|
|
||||||
@Comment("Column for storing player LastLocation - X")
|
@Comment("Column for storing player LastLocation - X")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_LASTLOC_X =
|
||||||
public String mySQLColumnLastLocX = "x";
|
newProperty(STRING, "DataSource.mySQLlastlocX", "x");
|
||||||
|
|
||||||
@Comment("Column for storing player LastLocation - Y")
|
@Comment("Column for storing player LastLocation - Y")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_LASTLOC_Y =
|
||||||
public String mySQLColumnLastLocY = "y";
|
newProperty(STRING, "DataSource.mySQLlastlocY", "y");
|
||||||
|
|
||||||
@Comment("Column for storing player LastLocation - Z")
|
@Comment("Column for storing player LastLocation - Z")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_LASTLOC_Z =
|
||||||
public String mySQLColumnLastLocZ = "z";
|
newProperty(STRING, "DataSource.mySQLlastlocZ", "z");
|
||||||
|
|
||||||
@Comment("Column for storing player LastLocation - World Name")
|
@Comment("Column for storing player LastLocation - World Name")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_LASTLOC_WORLD =
|
||||||
public String mySQLColumnLastLocWorld = "world";
|
newProperty(STRING, "DataSource.mySQLlastlocWorld", "world");
|
||||||
|
|
||||||
@Comment("Column for storing players groups")
|
@Comment("Column for storing players groups")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MYSQL_COL_GROUP =
|
||||||
public String mySQLColumnGroup = "";
|
newProperty(STRING, "ExternalBoardOptions.mySQLColumnGroup", "");
|
||||||
|
|
||||||
@Comment("Enable this when you allow registration through a website")
|
@Comment("Enable this when you allow registration through a website")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> MYSQL_WEBSITE =
|
||||||
public boolean mySQLWebsite = false;
|
newProperty(BOOLEAN, "DataSource.mySQLWebsite", false);
|
||||||
|
|
||||||
private static File configFile = new File("." + File.separator + "plugins" + File.separator + "AuthMe" + File.separator + "database.yml");
|
private DatabaseSettings() {
|
||||||
|
|
||||||
private DatabaseSettings instance;
|
|
||||||
|
|
||||||
public DatabaseSettings()
|
|
||||||
{
|
|
||||||
super(configFile);
|
|
||||||
instance = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DatabaseSettings getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInstance(DatabaseSettings instance) {
|
|
||||||
this.instance = instance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,83 +1,72 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
|
import fr.xephi.authme.settings.custom.domain.SettingsClass;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Comment;
|
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
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")
|
@Comment("Email SMTP server host")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> SMTP_HOST =
|
||||||
public String mailSMTP = "smtp.gmail.com";
|
newProperty(STRING, "Email.mailSMTP", "smtp.gmail.com");
|
||||||
|
|
||||||
@Comment("Email SMTP server port")
|
@Comment("Email SMTP server port")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> SMTP_PORT =
|
||||||
public int mailPort = 465;
|
newProperty(INTEGER, "Email.mailPort", 465);
|
||||||
|
|
||||||
@Comment("Email account whose send the mail")
|
@Comment("Email account which sends the mails")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MAIL_ACCOUNT =
|
||||||
public String mailAccount = "";
|
newProperty(STRING, "Email.mailAccount", "");
|
||||||
|
|
||||||
@Comment("Email account password")
|
@Comment("Email account password")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> MAIL_PASSWORD =
|
||||||
public String mailPassword = "";
|
newProperty(STRING, "Email.mailPassword", "");
|
||||||
|
|
||||||
@Comment("Random password length")
|
@Comment("Recovery password length")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> RECOVERY_PASSWORD_LENGTH =
|
||||||
public int recoveryPasswordLength = 8;
|
newProperty(INTEGER, "Email.RecoveryPasswordLength", 8);
|
||||||
|
|
||||||
@Comment("Mail Subject")
|
@Comment("Mail Subject")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> RECOVERY_MAIL_SUBJECT =
|
||||||
public String mailSubject = "Your new AuthMe password";
|
newProperty(STRING, "Email.mailSubject", "Your new AuthMe password");
|
||||||
|
|
||||||
@Comment("Like maxRegPerIP but with email")
|
@Comment("Like maxRegPerIP but with email")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> MAX_REG_PER_EMAIL =
|
||||||
public int maxRegPerEmail = 1;
|
newProperty(INTEGER, "Email.maxRegPerEmail", 1);
|
||||||
|
|
||||||
@Comment("Recall players to add an email ?")
|
@Comment("Recall players to add an email?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> RECALL_PLAYERS =
|
||||||
public boolean recallPlayers = false;
|
newProperty(BOOLEAN, "Email.recallPlayers", false);
|
||||||
|
|
||||||
@Comment("Delay in minute for the recall scheduler")
|
@Comment("Delay in minute for the recall scheduler")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> DELAY_RECALL =
|
||||||
public int delayRecall = 5;
|
newProperty(INTEGER, "Email.delayRecall", 5);
|
||||||
|
|
||||||
@Comment("Blacklist these domains for emails")
|
@Comment("Blacklist these domains for emails")
|
||||||
@Type(SettingType.StringList)
|
public static final Property<List<String>> DOMAIN_BLACKLIST =
|
||||||
public List<String> emailBlackListed = new ArrayList<String>();
|
newProperty(STRING_LIST, "Email.emailBlacklisted", "10minutemail.com");
|
||||||
|
|
||||||
@Comment("Whitelist ONLY these domains for emails")
|
@Comment("Whitelist ONLY these domains for emails")
|
||||||
@Type(SettingType.StringList)
|
public static final Property<List<String>> DOMAIN_WHITELIST =
|
||||||
public List<String> emailWhiteListed = new ArrayList<String>();
|
newProperty(STRING_LIST, "Email.emailWhitelisted");
|
||||||
|
|
||||||
@Comment("Do we need to send new password draw in an image ?")
|
@Comment("Send the new password drawn in an image?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> PASSWORD_AS_IMAGE =
|
||||||
public boolean generateImage = false;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,39 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
import java.util.List;
|
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 static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
|
||||||
|
|
||||||
public class HooksSettings extends CustomSetting {
|
public class HooksSettings implements SettingsClass {
|
||||||
|
|
||||||
@Comment("Do we need to hook with multiverse for spawn checking?")
|
@Comment("Do we need to hook with multiverse for spawn checking?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> MULTIVERSE =
|
||||||
public boolean multiverse = true;
|
newProperty(PropertyType.BOOLEAN, "Hooks.multiverse", true);
|
||||||
|
|
||||||
@Comment("Do we need to hook with BungeeCord ?")
|
@Comment("Do we need to hook with BungeeCord?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> BUNGEECORD =
|
||||||
public boolean bungeecord = false;
|
newProperty(PropertyType.BOOLEAN, "Hooks.bungeecord", false);
|
||||||
|
|
||||||
@Comment("Send player to this BungeeCord server after register/login")
|
@Comment("Send player to this BungeeCord server after register/login")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> BUNGEECORD_SERVER =
|
||||||
public String sendPlayerTo = "";
|
newProperty(PropertyType.STRING, "bungeecord.server", "");
|
||||||
|
|
||||||
@Comment("Do we need to disable Essentials SocialSpy on join?")
|
@Comment("Do we need to disable Essentials SocialSpy on join?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> DISABLE_SOCIAL_SPY =
|
||||||
public boolean disableSocialSpy = false;
|
newProperty(PropertyType.BOOLEAN, "Hooks.disableSocialSpy", false);
|
||||||
|
|
||||||
@Comment("Do we need to force /motd Essentials command on join?")
|
@Comment("Do we need to force /motd Essentials command on join?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> USE_ESSENTIALS_MOTD =
|
||||||
public boolean useEssentialsMotd = false;
|
newProperty(PropertyType.BOOLEAN, "Hooks.useEssentialsMotd", false);
|
||||||
|
|
||||||
@Comment("Do we need to cache custom Attributes?")
|
@Comment("Do we need to cache custom Attributes?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> CACHE_CUSTOM_ATTRIBUTES =
|
||||||
public boolean customAttributes = false;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
148
src/main/java/fr/xephi/authme/settings/custom/NewSetting.java
Normal file
148
src/main/java/fr/xephi/authme/settings/custom/NewSetting.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,63 +1,46 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
|
import fr.xephi.authme.settings.custom.domain.SettingsClass;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Comment;
|
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
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 )")
|
public class ProtectionSettings implements SettingsClass {
|
||||||
@Type(SettingType.Boolean)
|
|
||||||
public boolean enableProtection = false;
|
@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",
|
@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 !"})
|
"PLEASE USE QUOTES!"})
|
||||||
@Type(SettingType.StringList)
|
public static final Property<List<String>> COUNTRIES_WHITELIST =
|
||||||
public List<String> countriesWhitelist = new ArrayList<String>();
|
newProperty(STRING_LIST, "Protection.countries", "US", "GB", "A1");
|
||||||
|
|
||||||
@Comment({"Countries not allowed to join the server and register",
|
@Comment({"Countries not allowed to join the server and register",
|
||||||
"PLEASE USE QUOTES !"})
|
"PLEASE USE QUOTES!"})
|
||||||
@Type(SettingType.StringList)
|
public static final Property<List<String>> COUNTRIES_BLACKLIST =
|
||||||
public List<String> countriesBlacklist = new ArrayList<String>();
|
newProperty(STRING_LIST, "Protection.countriesBlacklist");
|
||||||
|
|
||||||
@Comment("Do we need to enable automatic antibot system?")
|
@Comment("Do we need to enable automatic antibot system?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> ENABLE_ANTIBOT =
|
||||||
public boolean enableAntiBot = false;
|
newProperty(BOOLEAN, "Protection.enableAntiBot", false);
|
||||||
|
|
||||||
@Comment("Max number of player allowed to login in 5 secs before enable AntiBot system automatically")
|
@Comment("Max number of player allowed to login in 5 secs before enable AntiBot system automatically")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> ANTIBOT_SENSIBILITY =
|
||||||
public int antiBotSensibility = 5;
|
newProperty(INTEGER, "Protection.antiBotSensibility", 5);
|
||||||
|
|
||||||
@Comment("Duration in minutes of the antibot automatic system")
|
@Comment("Duration in minutes of the antibot automatic system")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> ANTIBOT_DURATION =
|
||||||
public int antiBotDuration = 10;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,49 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
import java.util.List;
|
import fr.xephi.authme.settings.custom.domain.SettingsClass;
|
||||||
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Comment;
|
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
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")
|
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> USE_AUTO_PURGE =
|
||||||
public boolean useAutoPurge = false;
|
newProperty(BOOLEAN, "Purge.useAutoPurge", false);
|
||||||
|
|
||||||
@Comment("Number of Days an account become Unused")
|
@Comment("Number of Days an account become Unused")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> DAYS_BEFORE_REMOVE_PLAYER =
|
||||||
public int daysBeforeRemovePlayer = 60;
|
newProperty(INTEGER, "Purge.daysBeforeRemovePlayer", 60);
|
||||||
|
|
||||||
@Comment("Do we need to remove the player.dat file during purge process?")
|
@Comment("Do we need to remove the player.dat file during purge process?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_PLAYER_DAT =
|
||||||
public boolean removePlayerDat = false;
|
newProperty(BOOLEAN, "Purge.removePlayerDat", false);
|
||||||
|
|
||||||
@Comment("Do we need to remove the Essentials/users/player.yml file during purge process?")
|
@Comment("Do we need to remove the Essentials/users/player.yml file during purge process?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_ESSENTIALS_FILES =
|
||||||
public boolean removeEssentialsFiles = false;
|
newProperty(BOOLEAN, "Purge.removeEssentialsFiles", false);
|
||||||
|
|
||||||
@Comment("World where are players.dat stores")
|
@Comment("World where are players.dat stores")
|
||||||
@Type(SettingType.String)
|
public static final Property<String> DEFAULT_WORLD =
|
||||||
public String defaultWorld = "world";
|
newProperty(STRING, "Purge.defaultWorld", "world");
|
||||||
|
|
||||||
@Comment("Do we need to remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge process ?")
|
@Comment("Do we need to remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge process ?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_LIMITED_CREATIVE_INVENTORIES =
|
||||||
public boolean removeLimiteCreativeInventories = false;
|
newProperty(BOOLEAN, "Purge.removeLimitedCreativesInventories", false);
|
||||||
|
|
||||||
@Comment("Do we need to remove the AntiXRayData/PlayerData/player file during purge process?")
|
@Comment("Do we need to remove the AntiXRayData/PlayerData/player file during purge process?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_ANTI_XRAY_FILE =
|
||||||
public boolean removeAntiXRayFile = false;
|
newProperty(BOOLEAN, "Purge.removeAntiXRayFile", false);
|
||||||
|
|
||||||
@Comment("Do we need to remove permissions?")
|
@Comment("Do we need to remove permissions?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_PERMISSIONS =
|
||||||
public boolean removePermissions = false;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,60 +1,51 @@
|
|||||||
package fr.xephi.authme.settings.custom;
|
package fr.xephi.authme.settings.custom;
|
||||||
|
|
||||||
import java.io.File;
|
import fr.xephi.authme.settings.custom.domain.Comment;
|
||||||
import java.util.ArrayList;
|
import fr.xephi.authme.settings.custom.domain.Property;
|
||||||
import java.util.List;
|
import fr.xephi.authme.settings.custom.domain.SettingsClass;
|
||||||
|
|
||||||
import fr.xephi.authme.settings.custom.annotations.Comment;
|
import static fr.xephi.authme.settings.custom.domain.Property.newProperty;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type;
|
import static fr.xephi.authme.settings.custom.domain.PropertyType.BOOLEAN;
|
||||||
import fr.xephi.authme.settings.custom.annotations.Type.SettingType;
|
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",
|
@Comment({"Stop the server if we can't contact the sql database",
|
||||||
"Take care with this, if you set that to false,",
|
"Take care with this, if you set this to false,",
|
||||||
"AuthMe automatically disable and the server is not protected!"})
|
"AuthMe will automatically disable and the server won't be protected!"})
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> STOP_SERVER_ON_PROBLEM =
|
||||||
public boolean stopServerOnProblem = true;
|
newProperty(BOOLEAN, "Security.SQLProblem.stopServer", true);
|
||||||
|
|
||||||
@Comment("/reload support")
|
@Comment("/reload support")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> USE_RELOAD_COMMAND_SUPPORT =
|
||||||
public boolean useReloadCommandSupport = true;
|
newProperty(BOOLEAN, "Security.ReloadCommand.useReloadCommandSupport", true);
|
||||||
|
|
||||||
@Comment("Remove Spam from Console ?")
|
@Comment("Remove spam from console?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_SPAM_FROM_CONSOLE =
|
||||||
public boolean removeSpamFromConsole = false;
|
newProperty(BOOLEAN, "Security.console.noConsoleSpam", false);
|
||||||
|
|
||||||
@Comment("Remove Password from Console ?")
|
@Comment("Remove passwords from console?")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> REMOVE_PASSWORD_FROM_CONSOLE =
|
||||||
public boolean removePasswordFromConsole = true;
|
newProperty(BOOLEAN, "Security.console.removePassword", true);
|
||||||
|
|
||||||
@Comment("Player need to put a captcha when he fails too lot the password")
|
@Comment("Player need to put a captcha when he fails too lot the password")
|
||||||
@Type(SettingType.Boolean)
|
public static final Property<Boolean> USE_CAPTCHA =
|
||||||
public boolean useCaptcha = false;
|
newProperty(BOOLEAN, "Security.captcha.useCaptcha", false);
|
||||||
|
|
||||||
@Comment("Max allowed tries before request a captcha")
|
@Comment("Max allowed tries before request a captcha")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> MAX_LOGIN_TRIES_BEFORE_CAPTCHA =
|
||||||
public int maxLoginTryBeforeCaptcha = 5;
|
newProperty(INTEGER, "Security.captcha.maxLoginTry", 5);
|
||||||
|
|
||||||
@Comment("Captcha length ")
|
@Comment("Captcha length")
|
||||||
@Type(SettingType.Int)
|
public static final Property<Integer> CAPTCHA_LENGTH =
|
||||||
public int captchaLength = 5;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -6,15 +6,12 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Comment for properties which are also included in the YAML file upon saving.
|
||||||
* Add a comment to a field value
|
|
||||||
*
|
|
||||||
* @author xephi59
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
public @interface Comment {
|
public @interface Comment {
|
||||||
|
|
||||||
public String[] value() default "";
|
String[] value() default "";
|
||||||
|
|
||||||
}
|
}
|
@ -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 + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package fr.xephi.authme.settings.custom.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker for classes that define {@link Property} fields.
|
||||||
|
*/
|
||||||
|
public interface SettingsClass {
|
||||||
|
}
|
@ -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 + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package fr.xephi.authme.util;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils class for collections.
|
* Utils class for collections.
|
||||||
@ -58,4 +59,17 @@ public final class CollectionUtils {
|
|||||||
public static <T> boolean isEmpty(Collection<T> coll) {
|
public static <T> boolean isEmpty(Collection<T> coll) {
|
||||||
return coll == null || coll.isEmpty();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,4 +117,12 @@ public final class StringUtils {
|
|||||||
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
src/test/resources/437-config-test.yml
Normal file
44
src/test/resources/437-config-test.yml
Normal 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
|
172
src/test/resources/437-write-test.yml
Normal file
172
src/test/resources/437-write-test.yml
Normal 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
|
Loading…
Reference in New Issue
Block a user