#450 Fix copying of JAR files to plugin folder

- Create SettingsMigrationService#copyFileFromResource (inspired from CustomSettings)
- Use new method to copy missing files in plugin folder from JAR
- Create YamlFileConfiguration inside NewSetting: FileConfiguration object provided by JavaPlugin#getConfig() sets default values from the JAR's config.yml :(
- Change ConsoleLogger to take logger from plugin (work in progress)
This commit is contained in:
ljacqu 2016-02-06 17:10:00 +01:00
parent 85868ca830
commit 99b7b80f1d
13 changed files with 156 additions and 86 deletions

View File

@ -97,6 +97,7 @@
<directory>src/main/resources/</directory> <directory>src/main/resources/</directory>
<includes> <includes>
<include>email.html</include> <include>email.html</include>
<include>welcome.txt</include>
</includes> </includes>
</resource> </resource>
<resource> <resource>

View File

@ -13,6 +13,7 @@ import java.util.logging.Logger;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import fr.xephi.authme.settings.SettingsMigrationService;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -207,11 +208,21 @@ public class AuthMe extends JavaPlugin {
// Set various instances // Set various instances
server = getServer(); server = getServer();
plugin = this; plugin = this;
ConsoleLogger.setLogger(getLogger());
setPluginInfos(); setPluginInfos();
// Load settings and custom configurations, if it fails, stop the server due to security reasons. // Load settings and custom configurations, if it fails, stop the server due to security reasons.
newSettings = createNewSetting(); newSettings = createNewSetting();
if (newSettings == null) {
ConsoleLogger.showError("Could not load configuration. Aborting.");
server.shutdown();
return;
}
ConsoleLogger.setLoggingOptions(newSettings.getProperty(SecuritySettings.USE_LOGGING),
new File(getDataFolder(), "authme.log"));
// Old settings manager
if (!loadSettings()) { if (!loadSettings()) {
server.shutdown(); server.shutdown();
setEnabled(false); setEnabled(false);
@ -425,7 +436,6 @@ public class AuthMe extends JavaPlugin {
private boolean loadSettings() { private boolean loadSettings() {
try { try {
settings = new Settings(this); settings = new Settings(this);
Settings.reload();
return true; return true;
} catch (Exception e) { } catch (Exception e) {
ConsoleLogger.logException("Can't load the configuration file... Something went wrong. " ConsoleLogger.logException("Can't load the configuration file... Something went wrong. "
@ -436,8 +446,10 @@ public class AuthMe extends JavaPlugin {
} }
private NewSetting createNewSetting() { private NewSetting createNewSetting() {
File configFile = new File(getDataFolder() + "config.yml"); File configFile = new File(getDataFolder(), "config.yml");
return new NewSetting(getConfig(), configFile, getDataFolder()); return SettingsMigrationService.copyFileFromResource(configFile, "config.yml")
? new NewSetting(configFile, getDataFolder())
: null;
} }
/** /**

View File

@ -1,16 +1,16 @@
package fr.xephi.authme; package fr.xephi.authme;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Wrapper;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.logging.Logger;
/** /**
* The plugin's static logger. * The plugin's static logger.
@ -19,21 +19,31 @@ public final class ConsoleLogger {
private static final String NEW_LINE = System.getProperty("line.separator"); private static final String NEW_LINE = System.getProperty("line.separator");
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("[MM-dd HH:mm:ss]"); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("[MM-dd HH:mm:ss]");
private static Logger logger;
private static Wrapper wrapper = Wrapper.getInstance(); private static boolean useLogging = false;
private static File logFile;
private ConsoleLogger() { private ConsoleLogger() {
// Service class // Service class
} }
public static void setLogger(Logger logger) {
ConsoleLogger.logger = logger;
}
public static void setLoggingOptions(boolean useLogging, File logFile) {
ConsoleLogger.useLogging = useLogging;
ConsoleLogger.logFile = logFile;
}
/** /**
* Print an info message. * Print an info message.
* *
* @param message String * @param message String
*/ */
public static void info(String message) { public static void info(String message) {
wrapper.getLogger().info(message); logger.info(message);
if (Settings.useLogging) { if (useLogging) {
writeLog(message); writeLog(message);
} }
} }
@ -44,8 +54,8 @@ public final class ConsoleLogger {
* @param message String * @param message String
*/ */
public static void showError(String message) { public static void showError(String message) {
wrapper.getLogger().warning(message); logger.warning(message);
if (Settings.useLogging) { if (useLogging) {
writeLog("ERROR: " + message); writeLog("ERROR: " + message);
} }
} }
@ -61,7 +71,7 @@ public final class ConsoleLogger {
dateTime = DATE_FORMAT.format(new Date()); dateTime = DATE_FORMAT.format(new Date());
} }
try { try {
Files.write(Settings.LOG_FILE.toPath(), (dateTime + ": " + message + NEW_LINE).getBytes(), Files.write(logFile.toPath(), (dateTime + ": " + message + NEW_LINE).getBytes(),
StandardOpenOption.APPEND, StandardOpenOption.APPEND,
StandardOpenOption.CREATE); StandardOpenOption.CREATE);
} catch (IOException ignored) { } catch (IOException ignored) {
@ -74,7 +84,7 @@ public final class ConsoleLogger {
* @param th The Throwable whose stack trace should be logged * @param th The Throwable whose stack trace should be logged
*/ */
public static void writeStackTrace(Throwable th) { public static void writeStackTrace(Throwable th) {
if (Settings.useLogging) { if (useLogging) {
writeLog(Throwables.getStackTraceAsString(th)); writeLog(Throwables.getStackTraceAsString(th));
} }
} }

View File

@ -24,7 +24,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static fr.xephi.authme.util.StringUtils.makePath; import static fr.xephi.authme.settings.SettingsMigrationService.copyFileFromResource;
/** /**
* The new settings manager. * The new settings manager.
@ -42,24 +42,14 @@ public class NewSetting {
/** /**
* Constructor. Checks the given {@link FileConfiguration} object for completeness. * Constructor. Checks the given {@link FileConfiguration} object for completeness.
* *
* @param configuration The configuration to interact with
* @param configFile The configuration file * @param configFile The configuration file
* @param pluginFolder The AuthMe plugin folder * @param pluginFolder The AuthMe plugin folder
*/ */
public NewSetting(FileConfiguration configuration, File configFile, File pluginFolder) { public NewSetting(File configFile, File pluginFolder) {
this.configuration = configuration; this.configuration = YamlConfiguration.loadConfiguration(configFile);
this.configFile = configFile; this.configFile = configFile;
this.pluginFolder = pluginFolder; this.pluginFolder = pluginFolder;
messagesFile = buildMessagesFile(); validateAndLoadOptions();
welcomeMessage = readWelcomeMessage();
emailMessage = readEmailMessage();
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap, pluginFolder)) {
ConsoleLogger.info("Merged new config options");
ConsoleLogger.info("Please check your config.yml file for new settings!");
save(propertyMap);
}
} }
/** /**
@ -131,6 +121,7 @@ public class NewSetting {
*/ */
public void reload() { public void reload() {
configuration = YamlConfiguration.loadConfiguration(configFile); configuration = YamlConfiguration.loadConfiguration(configFile);
validateAndLoadOptions();
} }
private void save(PropertyMap propertyMap) { private void save(PropertyMap propertyMap) {
@ -185,6 +176,19 @@ public class NewSetting {
} }
} }
private void validateAndLoadOptions() {
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap, pluginFolder)) {
ConsoleLogger.info("Merged new config options");
ConsoleLogger.info("Please check your config.yml file for new settings!");
save(propertyMap);
}
messagesFile = buildMessagesFile();
welcomeMessage = readWelcomeMessage();
emailMessage = readEmailMessage();
}
private <T> String toYaml(Property<T> property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) { private <T> String toYaml(Property<T> property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) {
String representation = property.toYaml(configuration, simpleYaml, singleQuoteYaml); String representation = property.toYaml(configuration, simpleYaml, singleQuoteYaml);
return join("\n" + indent(indent), representation.split("\\n")); return join("\n" + indent(indent), representation.split("\\n"));
@ -196,59 +200,45 @@ public class NewSetting {
if (messagesFile.exists()) { if (messagesFile.exists()) {
return messagesFile; return messagesFile;
} }
return buildMessagesFileFromCode("en");
return copyFileFromResource(messagesFile, buildMessagesFilePathFromCode(languageCode))
? messagesFile
: buildMessagesFileFromCode("en");
} }
private File buildMessagesFileFromCode(String language) { private File buildMessagesFileFromCode(String language) {
return new File(pluginFolder, return new File(pluginFolder, buildMessagesFilePathFromCode(language));
makePath("messages", "messages_" + language + ".yml")); }
private static String buildMessagesFilePathFromCode(String language) {
return StringUtils.makePath("messages", "messages_" + language + ".yml");
} }
private List<String> readWelcomeMessage() { private List<String> readWelcomeMessage() {
if (getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { if (getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) {
final File welcomeFile = new File(pluginFolder, "welcome.txt"); final File welcomeFile = new File(pluginFolder, "welcome.txt");
final Charset charset = Charset.forName("UTF-8"); final Charset charset = Charset.forName("UTF-8");
if (!welcomeFile.exists()) { if (copyFileFromResource(welcomeFile, "welcome.txt")) {
try { try {
Files.write( return Files.readLines(welcomeFile, charset);
"Welcome {PLAYER} to {SERVER} server\n\nThis server uses AuthMe protection!",
welcomeFile, charset);
} catch (IOException e) { } catch (IOException e) {
ConsoleLogger.showError("Failed to create file '" + welcomeFile.getPath() + "': " ConsoleLogger.logException("Failed to read file '" + welcomeFile.getPath() + "':", e);
+ StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} }
} }
try {
return Files.readLines(welcomeFile, charset);
} catch (IOException e) {
ConsoleLogger.showError("Failed to read file '" + welcomeFile.getPath() + "': " +
StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
} }
return new ArrayList<>(0); return new ArrayList<>(0);
} }
private String readEmailMessage() { private String readEmailMessage() {
final File emailFile = new File(pluginFolder, "email.txt"); final File emailFile = new File(pluginFolder, "email.html");
final Charset charset = Charset.forName("UTF-8"); final Charset charset = Charset.forName("UTF-8");
if (!emailFile.exists()) { if (copyFileFromResource(emailFile, "email.html")) {
try { try {
Files.write("", emailFile, charset); return StringUtils.join("", Files.readLines(emailFile, charset));
} catch (IOException e) { } catch (IOException e) {
ConsoleLogger.showError("Failed to create file '" + emailFile.getPath() + "': " ConsoleLogger.logException("Failed to read file '" + emailFile.getPath() + "':", e);
+ StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} }
} }
try {
return StringUtils.join("", Files.readLines(emailFile, charset));
} catch (IOException e) {
ConsoleLogger.showError("Failed to read file '" + emailFile.getPath() + "': " +
StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
return ""; return "";
} }

View File

@ -6,7 +6,7 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSource.DataSourceType; import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.util.Wrapper; import fr.xephi.authme.util.Wrapper;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -87,7 +87,7 @@ public final class Settings {
getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup, getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup,
antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp, antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp,
getMaxJoinPerIp; getMaxJoinPerIp;
protected static YamlConfiguration configFile; protected static FileConfiguration configFile;
private static AuthMe plugin; private static AuthMe plugin;
private static Settings instance; private static Settings instance;
@ -99,25 +99,8 @@ public final class Settings {
public Settings(AuthMe pl) { public Settings(AuthMe pl) {
instance = this; instance = this;
plugin = pl; plugin = pl;
configFile = (YamlConfiguration) plugin.getConfig(); configFile = plugin.getConfig();
}
/**
* Method reload.
*
* @throws Exception if something went wrong
*/
public static void reload() throws Exception {
plugin.getLogger().info("Loading Configuration File...");
boolean exist = SETTINGS_FILE.exists();
if (!exist) {
plugin.saveDefaultConfig();
}
configFile.load(SETTINGS_FILE);
loadVariables(); loadVariables();
if (exist) {
instance.saveDefaults();
}
} }
public static void loadVariables() { public static void loadVariables() {

View File

@ -1,17 +1,20 @@
package fr.xephi.authme.settings; package fr.xephi.authme.settings;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Property; import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap; import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS; import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS;
import static java.lang.String.format;
/** /**
* Service for verifying that the configuration is up-to-date. * Service for verifying that the configuration is up-to-date.
@ -86,15 +89,51 @@ public final class SettingsMigrationService {
} }
final File emailFile = new File(dataFolder, "email.html"); final File emailFile = new File(dataFolder, "email.html");
final String mailText = configuration.getString(oldSettingPath)
.replace("<playername>", "<playername />")
.replace("<servername>", "<servername />")
.replace("<generatedpass>", "<generatedpass />")
.replace("<image>", "<image />");
if (!emailFile.exists()) { if (!emailFile.exists()) {
try (FileWriter fw = new FileWriter(emailFile)) { try (FileWriter fw = new FileWriter(emailFile)) {
fw.write(configuration.getString("Email.mailText")); fw.write(mailText);
} catch (IOException e) { } catch (IOException e) {
ConsoleLogger.showError("Could not create email.html configuration file: " ConsoleLogger.logException("Could not create email.html configuration file:", e);
+ StringUtils.formatException(e));
} }
} }
return true; return true;
} }
/**
* Copy a resource file (from the JAR) to the given file if it doesn't exist.
*
* @param destinationFile The file to check and copy to (outside of JAR)
* @param resourcePath Absolute path to the resource file (path to file within JAR)
* @return False if the file does not exist and could not be copied, true otherwise
*/
public static boolean copyFileFromResource(File destinationFile, String resourcePath) {
if (destinationFile.exists()) {
return true;
} else if (!destinationFile.getParentFile().exists() && !destinationFile.getParentFile().mkdirs()) {
ConsoleLogger.showError("Cannot create parent directories for '" + destinationFile + "'");
return false;
}
// ClassLoader#getResourceAsStream does not deal with the '\' path separator: replace to '/'
final String normalizedPath = resourcePath.replace("\\", "/");
try (InputStream is = AuthMe.class.getClassLoader().getResourceAsStream(normalizedPath)) {
if (is == null) {
ConsoleLogger.showError(format("Cannot copy resource '%s' to file '%s': cannot load resource",
resourcePath, destinationFile.getPath()));
} else {
Files.copy(is, destinationFile.toPath());
return true;
}
} catch (IOException e) {
ConsoleLogger.logException(format("Cannot copy resource '%s' to file '%s':",
resourcePath, destinationFile.getPath()), e);
}
return false;
}
} }

View File

@ -0,0 +1,3 @@
Welcome {PLAYER} on {SERVER} server
This server uses AuthMeReloaded protection!

View File

@ -0,0 +1,20 @@
package fr.xephi.authme;
import org.mockito.Mockito;
import java.util.logging.Logger;
/**
* Test initializer for {@link ConsoleLogger}.
*/
public class ConsoleLoggerTestInitializer {
private ConsoleLoggerTestInitializer() {
}
public static Logger setupLogger() {
Logger logger = Mockito.mock(Logger.class);
ConsoleLogger.setLogger(logger);
return logger;
}
}

View File

@ -219,8 +219,8 @@ public class Log4JFilterTest {
* Mocks a {@link Message} object and makes it return the given formatted message. * Mocks a {@link Message} object and makes it return the given formatted message.
* *
* @param formattedMessage the formatted message the mock should return * @param formattedMessage the formatted message the mock should return
* @return Message mock
* @return Message mock */ */
private static Message mockMessage(String formattedMessage) { private static Message mockMessage(String formattedMessage) {
Message message = Mockito.mock(Message.class); Message message = Mockito.mock(Message.class);
when(message.getFormattedMessage()).thenReturn(formattedMessage); when(message.getFormattedMessage()).thenReturn(formattedMessage);

View File

@ -1,9 +1,11 @@
package fr.xephi.authme.output; package fr.xephi.authme.output;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.util.WrapperMock; import fr.xephi.authme.util.WrapperMock;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -27,6 +29,12 @@ public class MessagesIntegrationTest {
private static final String YML_TEST_FILE = "messages_test.yml"; private static final String YML_TEST_FILE = "messages_test.yml";
private Messages messages; private Messages messages;
@BeforeClass
public static void setup() {
WrapperMock.createInstance();
ConsoleLoggerTestInitializer.setupLogger();
}
/** /**
* Loads the messages in the file {@code messages_test.yml} in the test resources folder. * Loads the messages in the file {@code messages_test.yml} in the test resources folder.
* The file does not contain all messages defined in {@link MessageKey} and its contents * The file does not contain all messages defined in {@link MessageKey} and its contents
@ -34,8 +42,6 @@ public class MessagesIntegrationTest {
*/ */
@Before @Before
public void setUpMessages() { public void setUpMessages() {
WrapperMock.createInstance();
URL url = getClass().getClassLoader().getResource(YML_TEST_FILE); URL url = getClass().getClassLoader().getResource(YML_TEST_FILE);
if (url == null) { if (url == null) {
throw new RuntimeException("File '" + YML_TEST_FILE + "' could not be loaded"); throw new RuntimeException("File '" + YML_TEST_FILE + "' could not be loaded");

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.WrapperMock; import fr.xephi.authme.util.WrapperMock;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -13,6 +14,7 @@ public class BcryptTest extends AbstractEncryptionMethodTest {
public static void setUpSettings() { public static void setUpSettings() {
WrapperMock.createInstance(); WrapperMock.createInstance();
Settings.bCryptLog2Rounds = 8; Settings.bCryptLog2Rounds = 8;
ConsoleLoggerTestInitializer.setupLogger();
} }
public BcryptTest() { public BcryptTest() {

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts; package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.util.WrapperMock; import fr.xephi.authme.util.WrapperMock;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -9,8 +10,9 @@ import org.junit.BeforeClass;
public class XFBCRYPTTest extends AbstractEncryptionMethodTest { public class XFBCRYPTTest extends AbstractEncryptionMethodTest {
@BeforeClass @BeforeClass
public static void setUpWrapper() { public static void setup() {
WrapperMock.createInstance(); WrapperMock.createInstance();
ConsoleLoggerTestInitializer.setupLogger();
} }
public XFBCRYPTTest() { public XFBCRYPTTest() {

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.util; package fr.xephi.authme.util;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.ReflectionTestUtils; import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.NewSetting;
@ -39,6 +40,7 @@ public class UtilsTest {
public static void setUpMocks() { public static void setUpMocks() {
WrapperMock wrapperMock = WrapperMock.createInstance(); WrapperMock wrapperMock = WrapperMock.createInstance();
authMeMock = wrapperMock.getAuthMe(); authMeMock = wrapperMock.getAuthMe();
ConsoleLoggerTestInitializer.setupLogger();
} }
@Before @Before