diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index efe629a2..ad488d69 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -42,6 +42,7 @@ import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -100,6 +101,7 @@ public class AuthMe extends JavaPlugin { private Messages messages; private JsonCache playerBackup; private ModuleManager moduleManager; + private PasswordSecurity passwordSecurity; // Public Instances public NewAPI api; @@ -206,12 +208,24 @@ public class AuthMe extends JavaPlugin { return; } - // Set up messages + // Set up messages & password security messages = Messages.getInstance(); + passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash, Settings.supportOldPassword); + + // Connect to the database and setup tables + try { + setupDatabase(); + } catch (Exception e) { + ConsoleLogger.writeStackTrace(e); + ConsoleLogger.showError(e.getMessage()); + ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); + stopOrUnload(); + return; + } // Set up the permissions manager and command handler permsMan = initializePermissionsManager(); - commandHandler = initializeCommandHandler(permsMan, messages); + commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); // Set up the module manager setupModuleManager(); @@ -253,16 +267,7 @@ public class AuthMe extends JavaPlugin { // Do a backup on start new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START); - // Connect to the database and setup tables - try { - setupDatabase(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - ConsoleLogger.showError(e.getMessage()); - ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); - stopOrUnload(); - return; - } + // Setup the inventory backup playerBackup = new JsonCache(); @@ -409,11 +414,12 @@ public class AuthMe extends JavaPlugin { } } - private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages) { + private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages, + PasswordSecurity passwordSecurity) { HelpProvider helpProvider = new HelpProvider(permissionsManager); Set baseCommands = CommandInitializer.buildCommands(); CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider); - CommandService commandService = new CommandService(this, mapper, helpProvider, messages); + CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity); return new CommandHandler(commandService); } @@ -563,14 +569,15 @@ public class AuthMe extends JavaPlugin { int accounts = database.getAccountsRegistered(); if (accounts >= 4000) { ConsoleLogger.showError("YOU'RE USING THE SQLITE DATABASE WITH " - + accounts + "+ ACCOUNTS, FOR BETTER PERFORMANCES, PLEASE UPGRADE TO MYSQL!!"); + + accounts + "+ ACCOUNTS; FOR BETTER PERFORMANCE, PLEASE UPGRADE TO MYSQL!!"); } } }); } if (Settings.getDataSource == DataSource.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!"); + 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); DataSource source = converter.run(); if (source != null) { @@ -579,12 +586,12 @@ public class AuthMe extends JavaPlugin { } // TODO: Move this to another place maybe ? - if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) - { - ConsoleLogger.showError("Your HashAlgorithm has been detected has plaintext and is now deprecrated, it will be changed and hashed now to AuthMe default hashing method"); - for (PlayerAuth auth : database.getAllAuths()) - { - auth.setHash(PasswordSecurity.getHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname())); + if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) { + 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()) { + HashResult hashResult = passwordSecurity.computeHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname()); + auth.setHash(hashResult.getHash()); database.updatePassword(auth); } Settings.setValue("settings.security.passwordHash", "SHA256"); @@ -973,4 +980,8 @@ public class AuthMe extends JavaPlugin { return database; } + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; + } + } diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 908f75df..09d47b0c 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -3,8 +3,7 @@ package fr.xephi.authme.api; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -12,9 +11,8 @@ import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import java.security.NoSuchAlgorithmException; - /** + * The current API of AuthMe. */ public class NewAPI { @@ -42,33 +40,36 @@ public class NewAPI { /** * Hook into AuthMe * - * @return AuthMe plugin + * @return The API object */ public static NewAPI getInstance() { - if (singleton != null) + if (singleton != null) { return singleton; + } Plugin p = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); if (p == null || !(p instanceof AuthMe)) { return null; } AuthMe authme = (AuthMe) p; - singleton = (new NewAPI(authme)); + singleton = new NewAPI(authme); return singleton; } /** - * Method getPlugin. + * Return the plugin instance. * - * @return AuthMe + * @return The AuthMe instance */ public AuthMe getPlugin() { return plugin; } /** - * @param player + * Return whether the given player is authenticated. * - * @return true if player is authenticate + * @param player The player to verify + * + * @return true if the player is authenticated */ public boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); @@ -93,15 +94,15 @@ public class NewAPI { } /** - * Method getLastLocation. + * Get the last location of a player. * - * @param player Player + * @param player Player The player to process * - * @return Location + * @return Location The location of the player */ public Location getLastLocation(Player player) { try { - PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); + PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName()); if (auth != null) { return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ()); @@ -121,81 +122,81 @@ public class NewAPI { */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return plugin.database.isAuthAvailable(player); + return plugin.getDataSource().isAuthAvailable(player); } /** - * @param playerName String - * @param passwordToCheck String + * Check the password for the given player. * - * @return true if the password is correct , false else + * @param playerName The player to check the password for + * @param passwordToCheck The password to check + * + * @return true if the password is correct, false otherwise */ public boolean checkPassword(String playerName, String passwordToCheck) { - if (!isRegistered(playerName)) - return false; - String player = playerName.toLowerCase(); - PlayerAuth auth = plugin.database.getAuth(player); - try { - return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName); - } catch (NoSuchAlgorithmException e) { + if (!isRegistered(playerName)) { return false; } + String player = playerName.toLowerCase(); + PlayerAuth auth = plugin.getDataSource().getAuth(player); + return plugin.getPasswordSecurity().comparePassword(passwordToCheck, auth.getHash(), playerName); } /** - * Register a player + * Register a player. * - * @param playerName String - * @param password String + * @param playerName The player to register + * @param password The password to register the player with * - * @return true if the player is register correctly + * @return true if the player was registered successfully */ public boolean registerPlayer(String playerName, String password) { - try { - String name = playerName.toLowerCase(); - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - if (isRegistered(name)) { - return false; - } - PlayerAuth auth = new PlayerAuth(name, hash, "192.168.0.1", 0, "your@email.com", playerName); - return plugin.database.saveAuth(auth); - } catch (NoSuchAlgorithmException ex) { + String name = playerName.toLowerCase(); + HashResult result = plugin.getPasswordSecurity().computeHash(password, name); + if (isRegistered(name)) { return false; } + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .hash(result.getHash()) + .salt(result.getSalt()) + .realName(playerName) + .build(); + return plugin.getDataSource().saveAuth(auth); } /** - * Force a player to login + * Force a player to login. * - * @param player * player + * @param player The player to log in */ public void forceLogin(Player player) { plugin.getManagement().performLogin(player, "dontneed", true); } /** - * Force a player to logout + * Force a player to logout. * - * @param player * player + * @param player The player to log out */ public void forceLogout(Player player) { plugin.getManagement().performLogout(player); } /** - * Force a player to register + * Force a player to register. * - * @param player * player - * @param password String + * @param player The player to register + * @param password The password to use */ public void forceRegister(Player player, String password) { plugin.getManagement().performRegister(player, password, null); } /** - * Force a player to unregister + * Force a player to unregister. * - * @param player * player + * @param player The player to unregister */ public void forceUnregister(Player player) { plugin.getManagement().performUnregister(player, "", true); diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java index 4e70a1fc..b53fc2e8 100644 --- a/src/main/java/fr/xephi/authme/command/CommandService.java +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -7,6 +7,7 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import java.util.List; @@ -21,6 +22,7 @@ public class CommandService { private final Messages messages; private final HelpProvider helpProvider; private final CommandMapper commandMapper; + private final PasswordSecurity passwordSecurity; /** * Constructor. @@ -30,11 +32,13 @@ public class CommandService { * @param helpProvider Help provider * @param messages Messages instance */ - public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages) { + public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages, + PasswordSecurity passwordSecurity) { this.authMe = authMe; this.messages = messages; this.helpProvider = helpProvider; this.commandMapper = commandMapper; + this.passwordSecurity = passwordSecurity; } /** @@ -91,6 +95,15 @@ public class CommandService { return authMe.getDataSource(); } + /** + * Return the PasswordSecurity instance. + * + * @return The password security instance + */ + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; + } + /** * Output the help for a given command. * diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 1905d838..44d8967e 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -8,13 +7,11 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -60,13 +57,6 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { @Override public void run() { DataSource dataSource = commandService.getDataSource(); - String hash; - try { - hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - } catch (NoSuchAlgorithmException e) { - commandService.send(sender, MessageKey.ERROR); - return; - } PlayerAuth auth = null; if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { auth = PlayerCache.getInstance().getAuth(playerNameLowerCase); @@ -77,17 +67,21 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.UNKNOWN_USER); return; } - auth.setHash(hash); + + // TODO #358: Do we always pass lowercase name?? In that case we need to do that in PasswordSecurity! + HashResult hashResult = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); + auth.setHash(hashResult.getHash()); + auth.setSalt(hashResult.getSalt()); if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase)) { auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); commandService.getDataSource().updateSalt(auth); } if (!dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.ERROR); - return; + } else { + commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } - commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } }); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index 0a752c44..44c0be4a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -5,12 +5,11 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -41,7 +40,8 @@ public class RegisterAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); return; } - if (playerPassLowerCase.length() < Settings.getPasswordMinLen || playerPassLowerCase.length() > Settings.passwordMaxLength) { + if (playerPassLowerCase.length() < Settings.getPasswordMinLen + || playerPassLowerCase.length() > Settings.passwordMaxLength) { commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); return; } @@ -53,30 +53,30 @@ public class RegisterAdminCommand implements ExecutableCommand { @Override public void run() { - try { - if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { - commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); - return; - } - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - PlayerAuth auth = new PlayerAuth(playerNameLowerCase, hash, "192.168.0.1", 0L, "your@email.com", playerName); - if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase) && PasswordSecurity.userSalt.get(playerNameLowerCase) != null) - auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); - else auth.setSalt(""); - if (!commandService.getDataSource().saveAuth(auth)) { - commandService.send(sender, MessageKey.ERROR); - return; - } - commandService.getDataSource().setUnlogged(playerNameLowerCase); - if (Bukkit.getPlayerExact(playerName) != null) - Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); - commandService.send(sender, MessageKey.REGISTER_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + " registered"); - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - commandService.send(sender, MessageKey.ERROR); + if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { + commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); + return; } + HashResult hashResult = commandService.getPasswordSecurity() + .computeHash(playerPass, playerNameLowerCase); + PlayerAuth auth = PlayerAuth.builder() + .name(playerNameLowerCase) + .realName(playerName) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) + .build(); + if (!commandService.getDataSource().saveAuth(auth)) { + commandService.send(sender, MessageKey.ERROR); + return; + } + commandService.getDataSource().setUnlogged(playerNameLowerCase); + if (Bukkit.getPlayerExact(playerName) != null) { + Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); + } else { + commandService.send(sender, MessageKey.REGISTER_SUCCESS); + ConsoleLogger.info(playerName + " registered"); + } } }); } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 07f05bfc..045bf00a 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -1,20 +1,18 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; -import java.security.NoSuchAlgorithmException; import java.util.List; public class RecoverEmailCommand extends PlayerCommand { @@ -37,36 +35,33 @@ public class RecoverEmailCommand extends PlayerCommand { commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return; } - try { - String thePass = RandomString.generate(Settings.getRecoveryPassLength); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName); - PlayerAuth auth; - if (PlayerCache.getInstance().isAuthenticated(playerName)) { - auth = PlayerCache.getInstance().getAuth(playerName); - } else if (dataSource.isAuthAvailable(playerName)) { - auth = dataSource.getAuth(playerName); - } else { - commandService.send(player, MessageKey.UNKNOWN_USER); - return; - } - if (Settings.getmailAccount.equals("") || Settings.getmailAccount.isEmpty()) { - commandService.send(player, MessageKey.ERROR); - return; - } - if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") - || auth.getEmail().equalsIgnoreCase("your@email.com")) { - commandService.send(player, MessageKey.INVALID_EMAIL); - return; - } - auth.setHash(hashNew); - dataSource.updatePassword(auth); - plugin.mail.main(auth, thePass); - commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); - } catch (NoSuchAlgorithmException | NoClassDefFoundError ex) { - ConsoleLogger.showError(StringUtils.formatException(ex)); - commandService.send(player, MessageKey.ERROR); + String thePass = RandomString.generate(Settings.getRecoveryPassLength); + HashResult hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); + PlayerAuth auth; + if (PlayerCache.getInstance().isAuthenticated(playerName)) { + auth = PlayerCache.getInstance().getAuth(playerName); + } else if (dataSource.isAuthAvailable(playerName)) { + auth = dataSource.getAuth(playerName); + } else { + commandService.send(player, MessageKey.UNKNOWN_USER); + return; } + if (StringUtils.isEmpty(Settings.getmailAccount)) { + commandService.send(player, MessageKey.ERROR); + return; + } + + if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") + || auth.getEmail().equalsIgnoreCase("your@email.com")) { + commandService.send(player, MessageKey.INVALID_EMAIL); + return; + } + auth.setHash(hashNew.getHash()); + auth.setSalt(hashNew.getSalt()); + dataSource.updatePassword(auth); + plugin.mail.main(auth, thePass); + commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } else { commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); } diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index ab8b754f..15e26efc 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -6,6 +6,7 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -13,7 +14,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map.Entry; @@ -28,15 +28,12 @@ public class RakamakConverter implements Converter { public RakamakConverter(AuthMe instance, CommandSender sender) { this.instance = instance; - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } - public RakamakConverter getInstance() { - return this; - } - @Override + // TODO ljacqu 20151229: Restructure this into smaller portions public void run() { HashAlgorithm hash = Settings.getPasswordHash; boolean useIP = Settings.rakamakUseIp; @@ -45,7 +42,7 @@ public class RakamakConverter implements Converter { File source = new File(Settings.PLUGIN_FOLDER, fileName); File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName); HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + HashMap playerPSW = new HashMap<>(); try { BufferedReader users; BufferedReader ipFile; @@ -61,30 +58,30 @@ public class RakamakConverter implements Converter { } } ipFile.close(); + users = new BufferedReader(new FileReader(source)); + PasswordSecurity passwordSecurity = instance.getPasswordSecurity(); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); - try { - playerPSW.put(arguments[0], PasswordSecurity.getHash(hash, arguments[1], arguments[0])); - } catch (NoSuchAlgorithmException e) { - ConsoleLogger.showError(e.getMessage()); - } + HashResult hashResult = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); + playerPSW.put(arguments[0], hashResult); + } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPSW.entrySet()) { String playerName = m.getKey(); - String psw = playerPSW.get(playerName); - String ip; - if (useIP) { - ip = playerIP.get(playerName); - } else { - ip = "127.0.0.1"; - } - PlayerAuth auth = new PlayerAuth(playerName, psw, ip, System.currentTimeMillis(), playerName); - if (PasswordSecurity.userSalt.containsKey(playerName)) - auth.setSalt(PasswordSecurity.userSalt.get(playerName)); + HashResult psw = playerPSW.get(playerName); + String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; + PlayerAuth auth = PlayerAuth.builder() + .name(playerName) + .realName(playerName) + .ip(ip) + .hash(psw.getHash()) + .salt(psw.getSalt()) + .lastLogin(System.currentTimeMillis()) + .build(); database.saveAuth(auth); } ConsoleLogger.info("Rakamak database has been imported correctly"); 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 c2bbbd64..17138cc6 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -138,17 +138,18 @@ public class AsynchronousLogin { String hash = pAuth.getHash(); String email = pAuth.getEmail(); - boolean passwordVerified = true; - if (!forceLogin) - try { - passwordVerified = PasswordSecurity.comparePasswordWithHash(password, hash, realName); - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); - return; - } + boolean passwordVerified = forceLogin || plugin.getPasswordSecurity().comparePassword(password, hash, realName); + if (passwordVerified && player.isOnline()) { - PlayerAuth auth = new PlayerAuth(name, hash, getIP(), new Date().getTime(), email, realName); + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(realName) + .ip(getIP()) + .lastLogin(new Date().getTime()) + .email(email) + .hash(hash) + .salt(pAuth.getSalt()) + .build(); database.updateSession(auth); if (Settings.useCaptcha) { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index fb102ec9..9796dbb0 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -44,7 +44,10 @@ public class AsyncRegister { } else if (!Settings.isRegistrationEnabled) { m.send(player, MessageKey.REGISTRATION_DISABLED); return false; - } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") + || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") + || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + // TODO #308: Remove check for SQL keywords m.send(player, MessageKey.PASSWORD_MATCH_ERROR); return false; } else if (passLow.equalsIgnoreCase(player.getName())) { @@ -87,26 +90,25 @@ public class AsyncRegister { } } - private void emailRegister() throws Exception { + private void emailRegister() { if (Settings.getmaxRegPerEmail > 0 && !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) .locY(player.getLocation().getY()) .locZ(player.getLocation().getZ()) .email(email) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { @@ -122,18 +124,17 @@ public class AsyncRegister { } private void passwordRegister() throws Exception { - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final HashResult hashResult = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .hash(hashResult.getHash()) + .salt(hashResult.getSalt()) .ip(ip) .locWorld(player.getLocation().getWorld().getName()) .locX(player.getLocation().getX()) .locY(player.getLocation().getY()) .locZ(player.getLocation().getZ()) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 53c54b3e..83b8cf7e 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -62,55 +62,52 @@ public class AsynchronousUnregister { } public void process() { - try { - if (force || PasswordSecurity.comparePasswordWithHash(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { - if (!plugin.database.removeAuth(name)) { - m.send(player, MessageKey.ERROR); - return; + if (force || plugin.getPasswordSecurity().comparePassword(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { + if (!plugin.database.removeAuth(name)) { + m.send(player, MessageKey.ERROR); + return; + } + int timeOut = Settings.getRegistrationTimeout * 20; + if (Settings.isForcedRegistrationEnabled) { + Utils.teleportToSpawn(player); + player.saveData(); + PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); + if (!Settings.getRegisteredGroup.isEmpty()) { + Utils.setGroup(player, GroupType.UNREGISTERED); } - int timeOut = Settings.getRegistrationTimeout * 20; - if (Settings.isForcedRegistrationEnabled) { - Utils.teleportToSpawn(player); - player.saveData(); - PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); - if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, GroupType.UNREGISTERED); - } - LimboCache.getInstance().addLimboPlayer(player); - LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); - int interval = Settings.getWarnMessageInterval; - BukkitScheduler scheduler = plugin.getServer().getScheduler(); - if (timeOut != 0) { - BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, - new TimeoutTask(plugin, name, player), timeOut); - limboPlayer.setTimeoutTaskId(id); - } - limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, - new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); - m.send(player, MessageKey.UNREGISTERED_SUCCESS); - ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - return; - } - if (!Settings.unRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.UNREGISTERED); - } - PlayerCache.getInstance().removePlayer(name); - // check if Player cache File Exist and delete it, preventing - // duplication of items - if (playerCache.doesCacheExist(player)) { - playerCache.removeCache(player); - } - // Apply blind effect - if (Settings.applyBlindEffect) { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + LimboCache.getInstance().addLimboPlayer(player); + LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); + int interval = Settings.getWarnMessageInterval; + BukkitScheduler scheduler = plugin.getServer().getScheduler(); + if (timeOut != 0) { + BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, + new TimeoutTask(plugin, name, player), timeOut); + limboPlayer.setTimeoutTaskId(id); } + limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, + new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); m.send(player, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - Utils.teleportToSpawn(player); - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + return; } - } catch (NoSuchAlgorithmException ignored) { + if (!Settings.unRegisteredGroup.isEmpty()) { + Utils.setGroup(player, Utils.GroupType.UNREGISTERED); + } + PlayerCache.getInstance().removePlayer(name); + // check if Player cache File Exist and delete it, preventing + // duplication of items + if (playerCache.doesCacheExist(player)) { + playerCache.removeCache(player); + } + // Apply blind effect + if (Settings.applyBlindEffect) { + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + } + m.send(player, MessageKey.UNREGISTERED_SUCCESS); + ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); + Utils.teleportToSpawn(player); + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index d84b6870..125e92b9 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -4,7 +4,6 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; -import fr.xephi.authme.security.crypts.BCRYPT; import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; @@ -20,9 +19,13 @@ public class PasswordSecurity { @Deprecated public static final HashMap userSalt = new HashMap<>(); private final DataSource dataSource; + private final HashAlgorithm algorithm; + private final boolean supportOldAlgorithm; - public PasswordSecurity(DataSource dataSource) { + public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm, boolean supportOldAlgorithm) { this.dataSource = dataSource; + this.algorithm = algorithm; + this.supportOldAlgorithm = supportOldAlgorithm; } @Deprecated @@ -32,96 +35,7 @@ public class PasswordSecurity { @Deprecated public static String getHash(HashAlgorithm alg, String password, String playerName) throws NoSuchAlgorithmException { - EncryptionMethod method; - try { - if (alg != HashAlgorithm.CUSTOM) - method = alg.getClazz().newInstance(); - else method = null; - } catch (InstantiationException | IllegalAccessException e) { - throw new NoSuchAlgorithmException("Problem with hash algorithm '" + alg + "'", e); - } - String salt = ""; - switch (alg) { - case SHA256: - salt = createSalt(16); - break; - case MD5VB: - salt = createSalt(16); - break; - case XAUTH: - salt = createSalt(12); - break; - case MYBB: - salt = createSalt(8); - userSalt.put(playerName, salt); - break; - case IPB3: - salt = createSalt(5); - userSalt.put(playerName, salt); - break; - case PHPFUSION: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SALTED2MD5: - salt = createSalt(Settings.saltLength); - userSalt.put(playerName, salt); - break; - case JOOMLA: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case BCRYPT: - salt = BCRYPT.gensalt(Settings.bCryptLog2Rounds); - userSalt.put(playerName, salt); - break; - case WBB3: - salt = createSalt(40); - userSalt.put(playerName, salt); - break; - case WBB4: - salt = BCRYPT.gensalt(8); - userSalt.put(playerName, salt); - break; - case PBKDF2DJANGO: - case PBKDF2: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SMF: - return method.computeHash(password, null, playerName); - case PHPBB: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case BCRYPT2Y: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case SALTEDSHA512: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case MD5: - case SHA1: - case WHIRLPOOL: - case PLAINTEXT: - case SHA512: - case ROYALAUTH: - case CRAZYCRYPT1: - case DOUBLEMD5: - case WORDPRESS: - case CUSTOM: - break; - default: - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - } - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - method = event.getMethod(); - if (method == null) - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - return method.computeHash(password, salt, playerName); + return ""; } @Deprecated @@ -167,11 +81,19 @@ public class PasswordSecurity { return false; } + public HashResult computeHash(String password, String playerName) { + return computeHash(algorithm, password, playerName); + } + public HashResult computeHash(HashAlgorithm algorithm, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); return method.computeHash(password, playerName); } + public boolean comparePassword(String hash, String password, String playerName) { + return comparePassword(algorithm, hash, password, playerName); + } + public boolean comparePassword(HashAlgorithm algorithm, String hash, String password, String playerName) { EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); String salt = null; diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index c5c7df3b..6e332ebd 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -1,20 +1,18 @@ package fr.xephi.authme.task; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.HashResult; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; - -import java.security.NoSuchAlgorithmException; - public class ChangePasswordTask implements Runnable { private final AuthMe plugin; @@ -32,48 +30,41 @@ public class ChangePasswordTask implements Runnable { @Override public void run() { Messages m = plugin.getMessages(); - try { - final String name = player.getName().toLowerCase(); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, newPassword, name); - PlayerAuth auth = PlayerCache.getInstance().getAuth(name); - if (PasswordSecurity.comparePasswordWithHash(oldPassword, auth.getHash(), player.getName())) { - auth.setHash(hashNew); - if (PasswordSecurity.userSalt.containsKey(name) && PasswordSecurity.userSalt.get(name) != null) { - auth.setSalt(PasswordSecurity.userSalt.get(name)); - } else { - auth.setSalt(""); - } - if (!plugin.database.updatePassword(auth)) { - m.send(player, MessageKey.ERROR); - return; - } - plugin.database.updateSalt(auth); - PlayerCache.getInstance().updatePlayer(auth); - m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(player.getName() + " changed his password"); - if (Settings.bungee) - { - final String hash = auth.getHash(); - final String salt = auth.getSalt(); - plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + PasswordSecurity passwordSecurity = plugin.getPasswordSecurity(); - @Override - public void run() { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("Forward"); - out.writeUTF("ALL"); - out.writeUTF("AuthMe"); - out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - }); - } - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + final String name = player.getName().toLowerCase(); + PlayerAuth auth = PlayerCache.getInstance().getAuth(name); + if (passwordSecurity.comparePassword(oldPassword, auth.getHash(), player.getName())) { + HashResult hashResult = passwordSecurity.computeHash(newPassword, name); + auth.setHash(hashResult.getHash()); + auth.setSalt(hashResult.getSalt()); + + if (!plugin.getDataSource().updatePassword(auth)) { + m.send(player, MessageKey.ERROR); + return; } - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); + plugin.getDataSource().updateSalt(auth); + PlayerCache.getInstance().updatePlayer(auth); + m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(player.getName() + " changed his password"); + if (Settings.bungee) { + final String hash = auth.getHash(); + final String salt = auth.getSalt(); + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + + @Override + public void run() { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); + out.writeUTF("ALL"); + out.writeUTF("AuthMe"); + out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + }); + } + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java index 7780db7a..3b457291 100644 --- a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java @@ -2,10 +2,12 @@ package fr.xephi.authme.command; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; @@ -32,6 +34,7 @@ public class CommandServiceTest { private CommandMapper commandMapper; private HelpProvider helpProvider; private Messages messages; + private PasswordSecurity passwordSecurity; private CommandService commandService; @Before @@ -40,7 +43,8 @@ public class CommandServiceTest { commandMapper = mock(CommandMapper.class); helpProvider = mock(HelpProvider.class); messages = mock(Messages.class); - commandService = new CommandService(authMe, commandMapper, helpProvider, messages); + passwordSecurity = mock(PasswordSecurity.class); + commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity); } @Test @@ -110,9 +114,25 @@ public class CommandServiceTest { } @Test - @Ignore public void shouldGetDataSource() { - // TODO ljacqu 20151226: Cannot mock calls to fields + // given + DataSource dataSource = mock(DataSource.class); + given(authMe.getDataSource()).willReturn(dataSource); + + // when + DataSource result = commandService.getDataSource(); + + // then + assertThat(result, equalTo(dataSource)); + } + + @Test + public void shouldGetPasswordSecurity() { + // given/when + PasswordSecurity passwordSecurity = commandService.getPasswordSecurity(); + + // then + assertThat(passwordSecurity, equalTo(this.passwordSecurity)); } @Test