diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index f2595f37..56580cf9 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -17,7 +17,6 @@ import fr.xephi.authme.command.CommandInitializer; import fr.xephi.authme.command.CommandMapper; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.help.HelpProvider; -import fr.xephi.authme.converter.ForceFlatToSqlite; import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; @@ -43,9 +42,8 @@ import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.process.Management; -import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.security.crypts.SHA256; import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; @@ -58,9 +56,9 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.GeoLiteAPI; +import fr.xephi.authme.util.MigrationService; import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.Utils; -import fr.xephi.authme.util.Wrapper; import net.minelink.ctplus.CombatTagPlus; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; @@ -77,6 +75,7 @@ import org.bukkit.scheduler.BukkitTask; import java.io.File; import java.io.IOException; import java.net.URL; +import java.sql.SQLException; import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -105,7 +104,6 @@ public class AuthMe extends JavaPlugin { // Private Instances private static AuthMe plugin; private static Server server; - private static Wrapper wrapper = Wrapper.getInstance(); private Management management; private CommandHandler commandHandler = null; private PermissionsManager permsMan = null; @@ -117,9 +115,8 @@ public class AuthMe extends JavaPlugin { /* * Public Instances - * TODO: Encapsulation + * TODO #432: Encapsulation */ - public NewAPI api; public SendMailSSL mail; public DataManager dataManager; @@ -246,7 +243,7 @@ public class AuthMe extends JavaPlugin { // Connect to the database and setup tables try { - setupDatabase(); + setupDatabase(newSettings); } catch (Exception e) { ConsoleLogger.logException("Fatal error occurred during database connection! " + "Authme initialization aborted!", e); @@ -254,6 +251,7 @@ public class AuthMe extends JavaPlugin { return; } + MigrationService.changePlainTextToSha256(newSettings, database, new SHA256()); passwordSecurity = new PasswordSecurity(getDataSource(), newSettings.getProperty(SecuritySettings.PASSWORD_HASH), Bukkit.getPluginManager(), newSettings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH)); @@ -516,25 +514,42 @@ public class AuthMe extends JavaPlugin { } } - public void setupDatabase() throws Exception { - if (database != null) - database.close(); - // Backend MYSQL - FILE - SQLITE - SQLITEHIKARI - boolean isSQLite = false; - switch (newSettings.getProperty(DatabaseSettings.BACKEND)) { - case FILE: - database = new FlatFile(); - break; - case MYSQL: - database = new MySQL(newSettings); - break; - case SQLITE: - database = new SQLite(newSettings); - isSQLite = true; - break; + /** + * Sets up the data source. + * + * @param settings The settings instance + * @see AuthMe#database + */ + public void setupDatabase(NewSetting settings) throws ClassNotFoundException, SQLException { + if (this.database != null) { + this.database.close(); } - if (isSQLite) { + DataSourceType dataSourceType = settings.getProperty(DatabaseSettings.BACKEND); + DataSource dataSource; + switch (dataSourceType) { + case FILE: + dataSource = new FlatFile(); + break; + case MYSQL: + dataSource = new MySQL(settings); + break; + case SQLITE: + dataSource = new SQLite(settings); + break; + default: + throw new UnsupportedOperationException("Unknown data source type '" + dataSourceType + "'"); + } + + DataSource convertedSource = MigrationService.convertFlatfileToSqlite(newSettings, dataSource); + dataSource = convertedSource == null ? dataSource : convertedSource; + + if (newSettings.getProperty(DatabaseSettings.USE_CACHING)) { + dataSource = new CacheDataSource(dataSource); + } + + database = dataSource; + if (DataSourceType.SQLITE == dataSourceType) { server.getScheduler().runTaskAsynchronously(this, new Runnable() { @Override public void run() { @@ -546,34 +561,6 @@ public class AuthMe extends JavaPlugin { } }); } - - if (Settings.getDataSource == DataSourceType.FILE) { - ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed " + - "to SQLite... Connection will be impossible until conversion is done!"); - ForceFlatToSqlite converter = new ForceFlatToSqlite(database, newSettings); - DataSource source = converter.run(); - if (source != null) { - database = source; - } - } - - // TODO: Move this to another place maybe ? - if (HashAlgorithm.PLAINTEXT == newSettings.getProperty(SecuritySettings.PASSWORD_HASH)) { - ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated; " + - "it will be changed and hashed now to the AuthMe default hashing method"); - for (PlayerAuth auth : database.getAllAuths()) { - HashedPassword hashedPassword = passwordSecurity.computeHash( - HashAlgorithm.SHA256, auth.getPassword().getHash(), auth.getNickname()); - auth.setPassword(hashedPassword); - database.updatePassword(auth); - } - newSettings.setProperty(SecuritySettings.PASSWORD_HASH, HashAlgorithm.SHA256); - newSettings.save(); - } - - if (newSettings.getProperty(DatabaseSettings.USE_CACHING)) { - database = new CacheDataSource(database); - } } /** @@ -910,7 +897,7 @@ public class AuthMe extends JavaPlugin { String commandLabel, String[] args) { // Make sure the command handler has been initialized if (commandHandler == null) { - wrapper.getLogger().severe("AuthMe command handler is not available"); + getLogger().severe("AuthMe command handler is not available"); return false; } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java index 39acd6a1..3dce54e1 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ReloadCommand.java @@ -20,7 +20,10 @@ public class ReloadCommand implements ExecutableCommand { try { commandService.getSettings().reload(); commandService.reloadMessages(commandService.getSettings().getMessagesFile()); - plugin.setupDatabase(); + // TODO #432: We should not reload only certain plugin entities but actually reinitialize all elements, + // i.e. here in the future we might not have setupDatabase() but Authme.onEnable(), maybe after + // a call to some destructor method + plugin.setupDatabase(commandService.getSettings()); commandService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); } catch (Exception e) { sender.sendMessage("Error occurred during reload of AuthMe: aborting"); diff --git a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java index edb72ac8..133b492f 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java @@ -7,6 +7,7 @@ public enum DataSourceType { MYSQL, + @Deprecated FILE, SQLITE diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index 5a4cf425..09647dff 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -220,7 +220,7 @@ public class AsynchronousLogin { } } - public void displayOtherAccounts(PlayerAuth auth) { + private void displayOtherAccounts(PlayerAuth auth) { if (!Settings.displayOtherAccounts || auth == null) { return; } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 298d56be..1a8278e1 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -113,7 +113,7 @@ public class PasswordSecurity { */ private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) { try { - return HashAlgorithm.CUSTOM.equals(algorithm) + return HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm) ? null : algorithm.getClazz().newInstance(); } catch (InstantiationException | IllegalAccessException e) { diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java index 76441b1b..9f76dbbb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -11,7 +11,7 @@ import fr.xephi.authme.security.crypts.description.Usage; * and store the salt with the hash itself. */ @Recommendation(Usage.ACCEPTABLE) -@HasSalt(SaltType.TEXT) // See saltLength() for length +@HasSalt(SaltType.TEXT) // See getSaltLength() for length public abstract class HexSaltedMethod implements EncryptionMethod { public abstract int getSaltLength(); diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java index 32c439db..9c6a7866 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java @@ -20,7 +20,7 @@ public class BackupSettings implements SettingsClass { public static final Property ON_SERVER_STOP = newProperty("BackupSystem.OnServerStop", true); - @Comment(" Windows only mysql installation Path") + @Comment("Windows only mysql installation Path") public static final Property MYSQL_WINDOWS_PATH = newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\"); diff --git a/src/main/java/fr/xephi/authme/util/MigrationService.java b/src/main/java/fr/xephi/authme/util/MigrationService.java new file mode 100644 index 00000000..6da4d95e --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/MigrationService.java @@ -0,0 +1,72 @@ +package fr.xephi.authme.util; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.converter.ForceFlatToSqlite; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.datasource.DataSourceType; +import fr.xephi.authme.security.HashAlgorithm; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.security.crypts.SHA256; +import fr.xephi.authme.settings.NewSetting; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.SecuritySettings; + +import java.util.List; + +/** + * Migrations to perform during the initialization of AuthMe. + */ +public final class MigrationService { + + private MigrationService() { + } + + /** + * Hash all passwords to SHA256 and updated the setting if the password hash is set to the deprecated PLAINTEXT. + * + * @param settings The settings instance + * @param dataSource The data source + * @param authmeSha256 Instance to the AuthMe SHA256 encryption method implementation + */ + public static void changePlainTextToSha256(NewSetting settings, DataSource dataSource, + SHA256 authmeSha256) { + if (HashAlgorithm.PLAINTEXT == settings.getProperty(SecuritySettings.PASSWORD_HASH)) { + ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated;" + + " it will be changed and hashed now to the AuthMe default hashing method"); + List allAuths = dataSource.getAllAuths(); + for (PlayerAuth auth : allAuths) { + HashedPassword hashedPassword = authmeSha256.computeHash( + auth.getPassword().getHash(), auth.getNickname()); + auth.setPassword(hashedPassword); + dataSource.updatePassword(auth); + } + settings.setProperty(SecuritySettings.PASSWORD_HASH, HashAlgorithm.SHA256); + settings.save(); + ConsoleLogger.info("Migrated " + allAuths.size() + " accounts from plaintext to SHA256"); + } + } + + /** + * Converts the data source from the deprecated FLATFILE type to SQLITE. + * + * @param settings The settings instance + * @param dataSource The data source + * @return The converted datasource (SQLite), or null if no migration was necessary + */ + public static DataSource convertFlatfileToSqlite(NewSetting settings, DataSource dataSource) { + if (DataSourceType.FILE == settings.getProperty(DatabaseSettings.BACKEND)) { + ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated; it will be changed " + + "to SQLite... Connection will be impossible until conversion is done!"); + ForceFlatToSqlite converter = new ForceFlatToSqlite(dataSource, settings); + DataSource result = converter.run(); + if (result == null) { + throw new IllegalStateException("Error during conversion from flatfile to SQLite"); + } else { + return result; + } + } + return null; + } + +}