package fr.xephi.authme.datasource.converter; import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.ConverterSettings; import fr.xephi.authme.util.UuidUtils; import org.bukkit.command.CommandSender; import javax.inject.Inject; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.UUID; import static fr.xephi.authme.util.Utils.logAndSendMessage; /** * Converts data from LoginSecurity to AuthMe. */ public class LoginSecurityConverter implements Converter { private final ConsoleLogger logger = ConsoleLoggerFactory.get(LoginSecurityConverter.class); private final File dataFolder; private final DataSource dataSource; private final boolean useSqlite; private final String mySqlHost; private final String mySqlDatabase; private final String mySqlUser; private final String mySqlPassword; @Inject LoginSecurityConverter(@DataFolder File dataFolder, DataSource dataSource, Settings settings) { this.dataFolder = dataFolder; this.dataSource = dataSource; useSqlite = settings.getProperty(ConverterSettings.LOGINSECURITY_USE_SQLITE); mySqlHost = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_HOST); mySqlDatabase = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_DATABASE); mySqlUser = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_USER); mySqlPassword = settings.getProperty(ConverterSettings.LOGINSECURITY_MYSQL_PASSWORD); } @Override public void execute(CommandSender sender) { try (Connection connection = createConnectionOrInformSender(sender)) { if (connection != null) { performConversion(sender, connection); logger.info("LoginSecurity conversion completed! Please remember to set \"legacyHashes: ['BCRYPT']\" " + "in your configuration file!"); } } catch (SQLException e) { sender.sendMessage("Failed to convert from SQLite. Please see the log for more info"); logger.logException("Could not fetch or migrate data:", e); } } /** * Performs the conversion from LoginSecurity to AuthMe. * * @param sender the command sender who launched the conversion * @param connection connection to the LoginSecurity data source */ @VisibleForTesting void performConversion(CommandSender sender, Connection connection) throws SQLException { try (Statement statement = connection.createStatement()) { statement.execute( "SELECT * from ls_players LEFT JOIN ls_locations ON ls_locations.id = ls_players.id"); try (ResultSet resultSet = statement.getResultSet()) { migrateData(sender, resultSet); } } } /** * Migrates the accounts. * * @param sender the command sender * @param resultSet result set with the account data to migrate */ private void migrateData(CommandSender sender, ResultSet resultSet) throws SQLException { List skippedPlayers = new ArrayList<>(); long successfulSaves = 0; while (resultSet.next()) { String name = resultSet.getString("last_name"); if (dataSource.isAuthAvailable(name)) { skippedPlayers.add(name); } else { PlayerAuth auth = buildAuthFromLoginSecurity(name, resultSet); dataSource.saveAuth(auth); dataSource.updateSession(auth); ++successfulSaves; } } logAndSendMessage(sender, "Migrated " + successfulSaves + " accounts successfully from LoginSecurity"); if (!skippedPlayers.isEmpty()) { logAndSendMessage(sender, "Skipped conversion for players which were already in AuthMe: " + String.join(", ", skippedPlayers)); } } /** * Creates a PlayerAuth based on data extracted from the given result set. * * @param name the name of the player to build * @param resultSet the result set to extract data from * @return the created player auth object */ private static PlayerAuth buildAuthFromLoginSecurity(String name, ResultSet resultSet) throws SQLException { Long lastLoginMillis = Optional.ofNullable(resultSet.getTimestamp("last_login")) .map(Timestamp::getTime).orElse(null); long regDate = Optional.ofNullable(resultSet.getDate("registration_date")) .map(Date::getTime).orElse(System.currentTimeMillis()); UUID uuid = UuidUtils.parseUuidSafely(resultSet.getString("unique_user_id")); return PlayerAuth.builder() .name(name) .realName(name) .password(resultSet.getString("password"), null) .lastIp(resultSet.getString("ip_address")) .lastLogin(lastLoginMillis) .registrationDate(regDate) .locX(resultSet.getDouble("x")) .locY(resultSet.getDouble("y")) .locZ(resultSet.getDouble("z")) .locWorld(resultSet.getString("world")) .locYaw(resultSet.getFloat("yaw")) .locPitch(resultSet.getFloat("pitch")) .uuid(uuid) .build(); } /** * Creates a {@link Connection} to the LoginSecurity data source based on the settings, * or informs the sender of the error that occurred. * * @param sender the command sender who launched the conversion * @return the created connection object, or null if it failed */ private Connection createConnectionOrInformSender(CommandSender sender) { Connection connection; if (useSqlite) { File sqliteDatabase = new File(dataFolder.getParentFile(), "LoginSecurity/LoginSecurity.db"); if (!sqliteDatabase.exists()) { sender.sendMessage("The file '" + sqliteDatabase.getPath() + "' does not exist"); return null; } connection = createSqliteConnection("plugins/LoginSecurity/LoginSecurity.db"); } else { if (mySqlDatabase.isEmpty() || mySqlUser.isEmpty()) { sender.sendMessage("The LoginSecurity database or username is not configured in AuthMe's config.yml"); return null; } connection = createMySqlConnection(); } if (connection == null) { sender.sendMessage("Could not connect to LoginSecurity using Sqlite = " + useSqlite + ", see log for more info"); return null; } return connection; } /** * Creates a connection to SQLite. * * @param path the path to the SQLite database * @return the created connection */ @VisibleForTesting Connection createSqliteConnection(String path) { try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } try { return DriverManager.getConnection( "jdbc:sqlite:" + path, "trump", "donald"); } catch (SQLException e) { logger.logException("Could not connect to SQLite database", e); return null; } } private Connection createMySqlConnection() { try { return DriverManager.getConnection( "jdbc:mysql://" + mySqlHost + "/" + mySqlDatabase, mySqlUser, mySqlPassword); } catch (SQLException e) { logger.logException("Could not connect to SQLite database", e); return null; } } }