diff --git a/pom.xml b/pom.xml index b872eb4cd..91a3d5d04 100644 --- a/pom.xml +++ b/pom.xml @@ -176,6 +176,18 @@ com.zaxxer.hikari fr.xephi.authme.libs.zaxxer.hikari + + mssql + fr.xephi.authme.libs.mssql + + + com.microsoft.sqlserver + fr.xephi.authme.libs.com.microsoft.sqlserver + + + microsoft.sql + fr.xephi.authme.libs.microsoft.sql + org.slf4j fr.xephi.authme.libs.slf4j.slf4j @@ -379,11 +391,22 @@ true + + + com.microsoft.sqlserver + mssql-jdbc + 6.3.0.jre8-preview + compile + true + + de.rtner PBKDF2 1.1.2 + compile + true @@ -423,6 +446,8 @@ org.bstats bstats-bukkit 1.0 + compile + true diff --git a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java index 093e88ed9..e5584195c 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSourceType.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSourceType.java @@ -7,6 +7,8 @@ public enum DataSourceType { MYSQL, + MSSQL, + SQLITE, @Deprecated diff --git a/src/main/java/fr/xephi/authme/datasource/MsSQL.java b/src/main/java/fr/xephi/authme/datasource/MsSQL.java new file mode 100644 index 000000000..1bcc1add2 --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/MsSQL.java @@ -0,0 +1,54 @@ +package fr.xephi.authme.datasource; + +import com.google.common.annotations.VisibleForTesting; +import com.zaxxer.hikari.HikariDataSource; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; +import fr.xephi.authme.settings.Settings; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +public class MsSQL extends SqlDataSource { + + public MsSQL(Settings settings, SqlExtensionsFactory extensionsFactory) throws SQLException { + super(settings, extensionsFactory); + } + + @VisibleForTesting + MsSQL(Settings settings, HikariDataSource hikariDataSource, SqlExtensionsFactory extensionsFactory) { + super(settings, hikariDataSource, extensionsFactory); + } + + @Override + protected void setConnectionArguments() { + super.setConnectionArguments(); + + // Database URL + ds.setDataSourceClassName("com.microsoft.sqlserver.jdbc.SQLServerDataSource"); + ds.addDataSourceProperty("serverName", host); + ds.addDataSourceProperty("port", port); + ds.addDataSourceProperty("databaseName", database); + + // Auth + ds.addDataSourceProperty("user", username); + ds.addDataSourceProperty("password", password); + } + + @Override + protected void checkTables() throws SQLException { + try (Connection con = getConnection(); Statement st = con.createStatement()) { + // Create table with ID column if it doesn't exist + String sql = "IF object_id('" + tableName + "') IS NOT NULL\n" + + " CREATE TABLE " + tableName + + " (" + col.ID + " INT CHECK (" + col.ID + " > 0) IDENTITY" + + ", PRIMARY KEY (" + col.ID + ")) CHARACTER SET = utf8;"; + st.executeUpdate(sql); + } + } + + @Override + public DataSourceType getType() { + return DataSourceType.MSSQL; + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 73f758403..463c9df6d 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -2,120 +2,36 @@ package fr.xephi.authme.datasource; import com.google.common.annotations.VisibleForTesting; import com.zaxxer.hikari.HikariDataSource; -import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; -import fr.xephi.authme.ConsoleLogger; -import fr.xephi.authme.data.auth.PlayerAuth; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; -import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; -import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.util.StringUtils; -import fr.xephi.authme.util.Utils; import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException; - -public class MySQL implements DataSource { +public class MySQL extends SqlDataSource { private boolean useSsl; - private String host; - private String port; - private String username; - private String password; - private String database; - private String tableName; - private int poolSize; - private int maxLifetime; - private List columnOthers; - private Columns col; - private MySqlExtension sqlExtension; - private HikariDataSource ds; - public MySQL(Settings settings, MySqlExtensionsFactory extensionsFactory) throws SQLException { - setParameters(settings, extensionsFactory); - - // Set the connection arguments (and check if connection is ok) - try { - this.setConnectionArguments(); - } catch (RuntimeException e) { - if (e instanceof IllegalArgumentException) { - ConsoleLogger.warning("Invalid database arguments! Please check your configuration!"); - ConsoleLogger.warning("If this error persists, please report it to the developer!"); - } - if (e instanceof PoolInitializationException) { - ConsoleLogger.warning("Can't initialize database connection! Please check your configuration!"); - ConsoleLogger.warning("If this error persists, please report it to the developer!"); - } - ConsoleLogger.warning("Can't use the Hikari Connection Pool! Please, report this error to the developer!"); - throw e; - } - - // Initialize the database - try { - checkTablesAndColumns(); - } catch (SQLException e) { - closeConnection(); - ConsoleLogger.logException("Can't initialize the MySQL database:", e); - ConsoleLogger.warning("Please check your database settings in the config.yml file!"); - throw e; - } + public MySQL(Settings settings, SqlExtensionsFactory extensionsFactory) throws SQLException { + super(settings, extensionsFactory); } @VisibleForTesting - MySQL(Settings settings, HikariDataSource hikariDataSource, MySqlExtensionsFactory extensionsFactory) { - ds = hikariDataSource; - setParameters(settings, extensionsFactory); + MySQL(Settings settings, HikariDataSource hikariDataSource, SqlExtensionsFactory extensionsFactory) { + super(settings, hikariDataSource, extensionsFactory); } - /** - * Retrieves various settings. - * - * @param settings the settings to read properties from - * @param extensionsFactory factory to create the MySQL extension - */ - private void setParameters(Settings settings, MySqlExtensionsFactory extensionsFactory) { - this.host = settings.getProperty(DatabaseSettings.MYSQL_HOST); - this.port = settings.getProperty(DatabaseSettings.MYSQL_PORT); - this.username = settings.getProperty(DatabaseSettings.MYSQL_USERNAME); - this.password = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD); - this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); - this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); - this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS); - this.col = new Columns(settings); - sqlExtension = extensionsFactory.buildExtension(col); - this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); - if (poolSize == -1) { - poolSize = Utils.getCoreCount() * 3; - } - this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME); + @Override + protected void setParameters(Settings settings, SqlExtensionsFactory extensionsFactory) { + super.setParameters(settings, extensionsFactory); this.useSsl = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL); } - /** - * Sets up the connection arguments to the database. - */ - private void setConnectionArguments() { - ds = new HikariDataSource(); - ds.setPoolName("AuthMeMYSQLPool"); - - // Pool Settings - ds.setMaximumPoolSize(poolSize); - ds.setMaxLifetime(maxLifetime * 1000); - + @Override + protected void setConnectionArguments() { + super.setConnectionArguments(); // Database URL ds.setJdbcUrl("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database); @@ -140,27 +56,10 @@ public class MySQL implements DataSource { ds.addDataSourceProperty("cachePrepStmts", "true"); ds.addDataSourceProperty("prepStmtCacheSize", "275"); ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - - ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); } @Override - public void reload() { - if (ds != null) { - ds.close(); - } - setConnectionArguments(); - ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); - } - - private Connection getConnection() throws SQLException { - return ds.getConnection(); - } - - /** - * Creates the table or any of its required columns if they don't exist. - */ - private void checkTablesAndColumns() throws SQLException { + protected void checkTables() throws SQLException { try (Connection con = getConnection(); Statement st = con.createStatement()) { // Create table with ID column if it doesn't exist String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" @@ -168,357 +67,6 @@ public class MySQL implements DataSource { + "PRIMARY KEY (" + col.ID + ")" + ") CHARACTER SET = utf8;"; st.executeUpdate(sql); - - DatabaseMetaData md = con.getMetaData(); - if (isColumnMissing(md, col.NAME)) { - st.executeUpdate("ALTER TABLE " + tableName - + " ADD COLUMN " + col.NAME + " VARCHAR(255) NOT NULL UNIQUE AFTER " + col.ID + ";"); - } - - if (isColumnMissing(md, col.REAL_NAME)) { - st.executeUpdate("ALTER TABLE " + tableName - + " ADD COLUMN " + col.REAL_NAME + " VARCHAR(255) NOT NULL AFTER " + col.NAME + ";"); - } - - if (isColumnMissing(md, col.PASSWORD)) { - st.executeUpdate("ALTER TABLE " + tableName - + " ADD COLUMN " + col.PASSWORD + " VARCHAR(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;"); - } - - if (!col.SALT.isEmpty() && isColumnMissing(md, col.SALT)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.SALT + " VARCHAR(255);"); - } - - if (isColumnMissing(md, col.IP)) { - st.executeUpdate("ALTER TABLE " + tableName - + " ADD COLUMN " + col.IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;"); - } - - if (isColumnMissing(md, col.LAST_LOGIN)) { - st.executeUpdate("ALTER TABLE " + tableName - + " ADD COLUMN " + col.LAST_LOGIN + " BIGINT NOT NULL DEFAULT 0;"); - } else { - migrateLastLoginColumn(con, md); - } - - if (isColumnMissing(md, col.LASTLOC_X)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LAST_LOGIN + " , ADD " - + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LASTLOC_X + " , ADD " - + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LASTLOC_Y); - } else { - st.executeUpdate("ALTER TABLE " + tableName + " MODIFY " - + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0', MODIFY " - + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0', MODIFY " - + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0';"); - } - - if (isColumnMissing(md, col.LASTLOC_WORLD)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT 'world' AFTER " + col.LASTLOC_Z); - } - - if (isColumnMissing(md, col.LASTLOC_YAW)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.LASTLOC_YAW + " FLOAT;"); - } - - if (isColumnMissing(md, col.LASTLOC_PITCH)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.LASTLOC_PITCH + " FLOAT;"); - } - - if (isColumnMissing(md, col.EMAIL)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.EMAIL + " VARCHAR(255) DEFAULT 'your@email.com' AFTER " + col.LASTLOC_WORLD); - } - - if (isColumnMissing(md, col.IS_LOGGED)) { - st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " - + col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.EMAIL); - } - } - ConsoleLogger.info("MySQL setup finished"); - } - - private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException { - try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) { - return !rs.next(); - } - } - - @Override - public boolean isAuthAvailable(String user) { - String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user.toLowerCase()); - try (ResultSet rs = pst.executeQuery()) { - return rs.next(); - } - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public HashedPassword getPassword(String user) { - boolean useSalt = !col.SALT.isEmpty(); - String sql = "SELECT " + col.PASSWORD - + (useSalt ? ", " + col.SALT : "") - + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user.toLowerCase()); - try (ResultSet rs = pst.executeQuery()) { - if (rs.next()) { - return new HashedPassword(rs.getString(col.PASSWORD), - useSalt ? rs.getString(col.SALT) : null); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return null; - } - - @Override - public PlayerAuth getAuth(String user) { - String sql = "SELECT * FROM " + tableName + " WHERE " + col.NAME + "=?;"; - PlayerAuth auth; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user.toLowerCase()); - try (ResultSet rs = pst.executeQuery()) { - if (rs.next()) { - int id = rs.getInt(col.ID); - auth = buildAuthFromResultSet(rs); - sqlExtension.extendAuth(auth, id, con); - return auth; - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return null; - } - - @Override - public boolean saveAuth(PlayerAuth auth) { - try (Connection con = getConnection()) { - String sql; - - boolean useSalt = !col.SALT.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt()); - sql = "INSERT INTO " + tableName + "(" - + col.NAME + "," + col.PASSWORD + "," + col.IP + "," - + col.LAST_LOGIN + "," + col.REAL_NAME + "," + col.EMAIL - + (useSalt ? "," + col.SALT : "") - + ") VALUES (?,?,?,?,?,?" + (useSalt ? ",?" : "") + ");"; - try (PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getPassword().getHash()); - pst.setString(3, auth.getIp()); - pst.setLong(4, auth.getLastLogin()); - pst.setString(5, auth.getRealName()); - pst.setString(6, auth.getEmail()); - if (useSalt) { - pst.setString(7, auth.getPassword().getSalt()); - } - pst.executeUpdate(); - } - - if (!columnOthers.isEmpty()) { - for (String column : columnOthers) { - try (PreparedStatement pst = con.prepareStatement("UPDATE " + tableName + " SET " + column + "=? WHERE " + col.NAME + "=?;")) { - pst.setString(1, auth.getRealName()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - } - } - } - - sqlExtension.saveAuth(auth, con); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public boolean updatePassword(PlayerAuth auth) { - return updatePassword(auth.getNickname(), auth.getPassword()); - } - - @Override - public boolean updatePassword(String user, HashedPassword password) { - user = user.toLowerCase(); - try (Connection con = getConnection()) { - boolean useSalt = !col.SALT.isEmpty(); - if (useSalt) { - String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;", - tableName, col.PASSWORD, col.SALT, col.NAME); - try (PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, password.getHash()); - pst.setString(2, password.getSalt()); - pst.setString(3, user); - pst.executeUpdate(); - } - } else { - String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;", - tableName, col.PASSWORD, col.NAME); - try (PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, password.getHash()); - pst.setString(2, user); - pst.executeUpdate(); - } - } - sqlExtension.changePassword(user, password, con); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public boolean updateSession(PlayerAuth auth) { - String sql = "UPDATE " + tableName + " SET " - + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, auth.getIp()); - pst.setLong(2, auth.getLastLogin()); - pst.setString(3, auth.getRealName()); - pst.setString(4, auth.getNickname()); - pst.executeUpdate(); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public Set getRecordsToPurge(long until, boolean includeEntriesWithLastLoginZero) { - Set list = new HashSet<>(); - String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_LOGIN + " < ?"; - if (!includeEntriesWithLastLoginZero) { - select += " AND " + col.LAST_LOGIN + " <> 0"; - } - try (Connection con = getConnection(); - PreparedStatement selectPst = con.prepareStatement(select)) { - selectPst.setLong(1, until); - try (ResultSet rs = selectPst.executeQuery()) { - while (rs.next()) { - list.add(rs.getString(col.NAME)); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - - return list; - } - - @Override - public boolean removeAuth(String user) { - user = user.toLowerCase(); - String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - sqlExtension.removeAuth(user, con); - pst.setString(1, user.toLowerCase()); - pst.executeUpdate(); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public boolean updateQuitLoc(PlayerAuth auth) { - String sql = "UPDATE " + tableName - + " SET " + col.LASTLOC_X + " =?, " + col.LASTLOC_Y + "=?, " + col.LASTLOC_Z + "=?, " - + col.LASTLOC_WORLD + "=?, " + col.LASTLOC_YAW + "=?, " + col.LASTLOC_PITCH + "=?" - + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setDouble(1, auth.getQuitLocX()); - pst.setDouble(2, auth.getQuitLocY()); - pst.setDouble(3, auth.getQuitLocZ()); - pst.setString(4, auth.getWorld()); - pst.setFloat(5, auth.getYaw()); - pst.setFloat(6, auth.getPitch()); - pst.setString(7, auth.getNickname()); - pst.executeUpdate(); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public boolean updateEmail(PlayerAuth auth) { - String sql = "UPDATE " + tableName + " SET " + col.EMAIL + " =? WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, auth.getEmail()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public void closeConnection() { - if (ds != null && !ds.isClosed()) { - ds.close(); - } - } - - @Override - public List getAllAuthsByIp(String ip) { - List result = new ArrayList<>(); - String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, ip); - try (ResultSet rs = pst.executeQuery()) { - while (rs.next()) { - result.add(rs.getString(col.NAME)); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return result; - } - - @Override - public int countAuthsByEmail(String email) { - String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE UPPER(" + col.EMAIL + ") = UPPER(?)"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, email); - try (ResultSet rs = pst.executeQuery()) { - if (rs.next()) { - return rs.getInt(1); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return 0; - } - - @Override - public void purgeRecords(Collection toPurge) { - String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - for (String name : toPurge) { - pst.setString(1, name.toLowerCase()); - pst.executeUpdate(); - } - } catch (SQLException ex) { - logSqlException(ex); } } @@ -526,235 +74,4 @@ public class MySQL implements DataSource { public DataSourceType getType() { return DataSourceType.MYSQL; } - - @Override - public boolean isLogged(String user) { - String sql = "SELECT " + col.IS_LOGGED + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user); - try (ResultSet rs = pst.executeQuery()) { - return rs.next() && (rs.getInt(col.IS_LOGGED) == 1); - } - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public void setLogged(String user) { - String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setInt(1, 1); - pst.setString(2, user.toLowerCase()); - pst.executeUpdate(); - } catch (SQLException ex) { - logSqlException(ex); - } - } - - @Override - public void setUnlogged(String user) { - String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setInt(1, 0); - pst.setString(2, user.toLowerCase()); - pst.executeUpdate(); - } catch (SQLException ex) { - logSqlException(ex); - } - } - - @Override - public void purgeLogged() { - String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setInt(1, 0); - pst.setInt(2, 1); - pst.executeUpdate(); - } catch (SQLException ex) { - logSqlException(ex); - } - } - - @Override - public int getAccountsRegistered() { - int result = 0; - String sql = "SELECT COUNT(*) FROM " + tableName; - try (Connection con = getConnection(); - Statement st = con.createStatement(); - ResultSet rs = st.executeQuery(sql)) { - if (rs.next()) { - result = rs.getInt(1); - } - } catch (SQLException ex) { - logSqlException(ex); - } - return result; - } - - @Override - public boolean updateRealName(String user, String realName) { - String sql = "UPDATE " + tableName + " SET " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, realName); - pst.setString(2, user); - pst.executeUpdate(); - return true; - } catch (SQLException ex) { - logSqlException(ex); - } - return false; - } - - @Override - public DataSourceResult getEmail(String user) { - String sql = "SELECT " + col.EMAIL + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; - try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { - pst.setString(1, user); - try (ResultSet rs = pst.executeQuery()) { - if (rs.next()) { - return DataSourceResult.of(rs.getString(1)); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return DataSourceResult.unknownPlayer(); - } - - @Override - public List getAllAuths() { - List auths = new ArrayList<>(); - try (Connection con = getConnection(); Statement st = con.createStatement()) { - try (ResultSet rs = st.executeQuery("SELECT * FROM " + tableName)) { - while (rs.next()) { - PlayerAuth auth = buildAuthFromResultSet(rs); - sqlExtension.extendAuth(auth, rs.getInt(col.ID), con); - auths.add(auth); - } - } - } catch (SQLException ex) { - logSqlException(ex); - } - return auths; - } - - @Override - public List getLoggedPlayersWithEmptyMail() { - List players = new ArrayList<>(); - String sql = "SELECT " + col.REAL_NAME + " FROM " + tableName + " WHERE " + col.IS_LOGGED + " = 1" - + " AND (" + col.EMAIL + " = 'your@email.com' OR " + col.EMAIL + " IS NULL);"; - try (Connection con = getConnection(); - Statement st = con.createStatement(); - ResultSet rs = st.executeQuery(sql)) { - while (rs.next()) { - players.add(rs.getString(1)); - } - } catch (SQLException ex) { - logSqlException(ex); - } - return players; - } - - private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { - String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT); - int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP); - return PlayerAuth.builder() - .name(row.getString(col.NAME)) - .realName(row.getString(col.REAL_NAME)) - .password(row.getString(col.PASSWORD), salt) - .lastLogin(row.getLong(col.LAST_LOGIN)) - .ip(row.getString(col.IP)) - .locWorld(row.getString(col.LASTLOC_WORLD)) - .locX(row.getDouble(col.LASTLOC_X)) - .locY(row.getDouble(col.LASTLOC_Y)) - .locZ(row.getDouble(col.LASTLOC_Z)) - .locYaw(row.getFloat(col.LASTLOC_YAW)) - .locPitch(row.getFloat(col.LASTLOC_PITCH)) - .email(row.getString(col.EMAIL)) - .groupId(group) - .build(); - } - - /** - * Checks if the last login column has a type that needs to be migrated. - * - * @param con connection to the database - * @param metaData lastlogin column meta data - * - * @throws SQLException . - */ - private void migrateLastLoginColumn(Connection con, DatabaseMetaData metaData) throws SQLException { - final int columnType; - try (ResultSet rs = metaData.getColumns(null, null, tableName, col.LAST_LOGIN)) { - if (!rs.next()) { - ConsoleLogger.warning("Could not get LAST_LOGIN meta data. This should never happen!"); - return; - } - columnType = rs.getInt("DATA_TYPE"); - } - - if (columnType == Types.TIMESTAMP) { - migrateLastLoginColumnFromTimestamp(con); - } else if (columnType == Types.INTEGER) { - migrateLastLoginColumnFromInt(con); - } - } - - /** - * Performs conversion of lastlogin column from timestamp type to bigint. - * - * @param con connection to the database - */ - private void migrateLastLoginColumnFromTimestamp(Connection con) throws SQLException { - ConsoleLogger.info("Migrating lastlogin column from timestamp to bigint"); - final String lastLoginOld = col.LAST_LOGIN + "_old"; - - // Rename lastlogin to lastlogin_old - String sql = String.format("ALTER TABLE %s CHANGE COLUMN %s %s BIGINT", - tableName, col.LAST_LOGIN, lastLoginOld); - try (PreparedStatement pst = con.prepareStatement(sql)) { - pst.execute(); - } - - // Create lastlogin column - sql = String.format("ALTER TABLE %s ADD COLUMN %s " - + "BIGINT NOT NULL DEFAULT 0 AFTER %s", - tableName, col.LAST_LOGIN, col.IP); - con.prepareStatement(sql).execute(); - - // Set values of lastlogin based on lastlogin_old - sql = String.format("UPDATE %s SET %s = UNIX_TIMESTAMP(%s) * 1000", - tableName, col.LAST_LOGIN, lastLoginOld); - con.prepareStatement(sql).execute(); - - // Drop lastlogin_old - sql = String.format("ALTER TABLE %s DROP COLUMN %s", - tableName, lastLoginOld); - con.prepareStatement(sql).execute(); - ConsoleLogger.info("Finished migration of lastlogin (timestamp to bigint)"); - } - - /** - * Performs conversion of lastlogin column from int to bigint. - * - * @param con connection to the database - */ - private void migrateLastLoginColumnFromInt(Connection con) throws SQLException { - // Change from int to bigint - ConsoleLogger.info("Migrating lastlogin column from int to bigint"); - String sql = String.format("ALTER TABLE %s MODIFY %s BIGINT;", tableName, col.LAST_LOGIN); - con.prepareStatement(sql).execute(); - - // Migrate timestamps in seconds format to milliseconds format if they are plausible - int rangeStart = 1262304000; // timestamp for 2010-01-01 - int rangeEnd = 1514678400; // timestamp for 2017-12-31 - sql = String.format("UPDATE %s SET %s = %s * 1000 WHERE %s > %d AND %s < %d;", - tableName, col.LAST_LOGIN, col.LAST_LOGIN, col.LAST_LOGIN, rangeStart, col.LAST_LOGIN, rangeEnd); - int changedRows = con.prepareStatement(sql).executeUpdate(); - - ConsoleLogger.warning("You may have entries with invalid timestamps. Please check your data " - + "before purging. " + changedRows + " rows were migrated from seconds to milliseconds."); - } } diff --git a/src/main/java/fr/xephi/authme/datasource/SqlDataSource.java b/src/main/java/fr/xephi/authme/datasource/SqlDataSource.java new file mode 100644 index 000000000..23c5b721d --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/SqlDataSource.java @@ -0,0 +1,728 @@ +package fr.xephi.authme.datasource; + +import com.google.common.annotations.VisibleForTesting; +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.sqlextensions.SqlExtension; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.properties.DatabaseSettings; +import fr.xephi.authme.settings.properties.HooksSettings; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.Utils; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException; + +public abstract class SqlDataSource implements DataSource { + + protected String host; + protected String port; + protected String username; + protected String password; + protected String database; + protected String tableName; + private int poolSize; + private int maxLifetime; + private List columnOthers; + protected Columns col; + private SqlExtension sqlExtension; + protected HikariDataSource ds; + + public SqlDataSource(Settings settings, SqlExtensionsFactory extensionsFactory) throws SQLException { + setParameters(settings, extensionsFactory); + + // Set the connection arguments (and check if connection is ok) + try { + this.setConnectionArguments(); + } catch (RuntimeException e) { + if (e instanceof IllegalArgumentException) { + ConsoleLogger.warning("Invalid database arguments! Please check your configuration!"); + ConsoleLogger.warning("If this error persists, please report it to the developer!"); + } + if (e instanceof PoolInitializationException) { + ConsoleLogger.warning("Can't initialize database connection! Please check your configuration!"); + ConsoleLogger.warning("If this error persists, please report it to the developer!"); + } + ConsoleLogger.warning("Can't use the Hikari Connection Pool! Please, report this error to the developer!"); + throw e; + } + + // Initialize the database + try { + checkTables(); + checkColumns(); + ConsoleLogger.info("MySQL setup finished"); + } catch (SQLException e) { + closeConnection(); + ConsoleLogger.logException("Can't initialize the SQL database:", e); + ConsoleLogger.warning("Please check your database settings in the config.yml file!"); + throw e; + } + } + + @VisibleForTesting + SqlDataSource(Settings settings, HikariDataSource hikariDataSource, SqlExtensionsFactory extensionsFactory) { + ds = hikariDataSource; + setParameters(settings, extensionsFactory); + } + + /** + * Retrieves various settings. + * + * @param settings the settings to read properties from + * @param extensionsFactory factory to create the MySQL extension + */ + protected void setParameters(Settings settings, SqlExtensionsFactory extensionsFactory) { + this.host = settings.getProperty(DatabaseSettings.MYSQL_HOST); + this.port = settings.getProperty(DatabaseSettings.MYSQL_PORT); + this.username = settings.getProperty(DatabaseSettings.MYSQL_USERNAME); + this.password = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD); + this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); + this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); + this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS); + this.col = new Columns(settings); + sqlExtension = extensionsFactory.buildExtension(col); + this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); + if (poolSize == -1) { + poolSize = Utils.getCoreCount() * 3; + } + this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME); + } + + /** + * Sets up the connection arguments to the database. + */ + protected void setConnectionArguments() { + ds = new HikariDataSource(); + ds.setPoolName("AuthMeSQLPool"); + + // Pool Settings + ds.setMaximumPoolSize(poolSize); + ds.setMaxLifetime(maxLifetime * 1000); + } + + @Override + public void reload() { + if (ds != null) { + ds.close(); + } + setConnectionArguments(); + ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); + } + + protected Connection getConnection() throws SQLException { + return ds.getConnection(); + } + + /** + * Creates the table if it don't exist. + */ + protected abstract void checkTables() throws SQLException; + + /** + * Creates any required column if they don't exist. + */ + private void checkColumns() throws SQLException { + try (Connection con = getConnection(); Statement st = con.createStatement()) { + DatabaseMetaData md = con.getMetaData(); + if (isColumnMissing(md, col.NAME)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.NAME + " VARCHAR(255) NOT NULL UNIQUE AFTER " + col.ID + ";"); + } + + if (isColumnMissing(md, col.REAL_NAME)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.REAL_NAME + " VARCHAR(255) NOT NULL AFTER " + col.NAME + ";"); + } + + if (isColumnMissing(md, col.PASSWORD)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.PASSWORD + " VARCHAR(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;"); + } + + if (!col.SALT.isEmpty() && isColumnMissing(md, col.SALT)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.SALT + " VARCHAR(255);"); + } + + if (isColumnMissing(md, col.IP)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;"); + } + + if (isColumnMissing(md, col.LAST_LOGIN)) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + col.LAST_LOGIN + " BIGINT NOT NULL DEFAULT 0;"); + } else { + migrateLastLoginColumn(con, md); + } + + if (isColumnMissing(md, col.LASTLOC_X)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LAST_LOGIN + " , ADD " + + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LASTLOC_X + " , ADD " + + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0' AFTER " + col.LASTLOC_Y); + } else { + st.executeUpdate("ALTER TABLE " + tableName + " MODIFY " + + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0', MODIFY " + + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0', MODIFY " + + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0';"); + } + + if (isColumnMissing(md, col.LASTLOC_WORLD)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT 'world' AFTER " + col.LASTLOC_Z); + } + + if (isColumnMissing(md, col.LASTLOC_YAW)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_YAW + " FLOAT;"); + } + + if (isColumnMissing(md, col.LASTLOC_PITCH)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.LASTLOC_PITCH + " FLOAT;"); + } + + if (isColumnMissing(md, col.EMAIL)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.EMAIL + " VARCHAR(255) DEFAULT 'your@email.com' AFTER " + col.LASTLOC_WORLD); + } + + if (isColumnMissing(md, col.IS_LOGGED)) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + + col.IS_LOGGED + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.EMAIL); + } + } + } + + private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException { + try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) { + return !rs.next(); + } + } + + @Override + public boolean isAuthAvailable(String user) { + String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user.toLowerCase()); + try (ResultSet rs = pst.executeQuery()) { + return rs.next(); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public HashedPassword getPassword(String user) { + boolean useSalt = !col.SALT.isEmpty(); + String sql = "SELECT " + col.PASSWORD + + (useSalt ? ", " + col.SALT : "") + + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user.toLowerCase()); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + return new HashedPassword(rs.getString(col.PASSWORD), + useSalt ? rs.getString(col.SALT) : null); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return null; + } + + @Override + public PlayerAuth getAuth(String user) { + String sql = "SELECT * FROM " + tableName + " WHERE " + col.NAME + "=?;"; + PlayerAuth auth; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user.toLowerCase()); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + int id = rs.getInt(col.ID); + auth = buildAuthFromResultSet(rs); + sqlExtension.extendAuth(auth, id, con); + return auth; + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return null; + } + + @Override + public boolean saveAuth(PlayerAuth auth) { + try (Connection con = getConnection()) { + String sql; + + boolean useSalt = !col.SALT.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt()); + sql = "INSERT INTO " + tableName + "(" + + col.NAME + "," + col.PASSWORD + "," + col.IP + "," + + col.LAST_LOGIN + "," + col.REAL_NAME + "," + col.EMAIL + + (useSalt ? "," + col.SALT : "") + + ") VALUES (?,?,?,?,?,?" + (useSalt ? ",?" : "") + ");"; + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, auth.getNickname()); + pst.setString(2, auth.getPassword().getHash()); + pst.setString(3, auth.getIp()); + pst.setLong(4, auth.getLastLogin()); + pst.setString(5, auth.getRealName()); + pst.setString(6, auth.getEmail()); + if (useSalt) { + pst.setString(7, auth.getPassword().getSalt()); + } + pst.executeUpdate(); + } + + if (!columnOthers.isEmpty()) { + for (String column : columnOthers) { + try (PreparedStatement pst = con.prepareStatement("UPDATE " + tableName + " SET " + column + "=? WHERE " + col.NAME + "=?;")) { + pst.setString(1, auth.getRealName()); + pst.setString(2, auth.getNickname()); + pst.executeUpdate(); + } + } + } + + sqlExtension.saveAuth(auth, con); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public boolean updatePassword(PlayerAuth auth) { + return updatePassword(auth.getNickname(), auth.getPassword()); + } + + @Override + public boolean updatePassword(String user, HashedPassword password) { + user = user.toLowerCase(); + try (Connection con = getConnection()) { + boolean useSalt = !col.SALT.isEmpty(); + if (useSalt) { + String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;", + tableName, col.PASSWORD, col.SALT, col.NAME); + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, password.getHash()); + pst.setString(2, password.getSalt()); + pst.setString(3, user); + pst.executeUpdate(); + } + } else { + String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;", + tableName, col.PASSWORD, col.NAME); + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, password.getHash()); + pst.setString(2, user); + pst.executeUpdate(); + } + } + sqlExtension.changePassword(user, password, con); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public boolean updateSession(PlayerAuth auth) { + String sql = "UPDATE " + tableName + " SET " + + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, auth.getIp()); + pst.setLong(2, auth.getLastLogin()); + pst.setString(3, auth.getRealName()); + pst.setString(4, auth.getNickname()); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public Set getRecordsToPurge(long until, boolean includeEntriesWithLastLoginZero) { + Set list = new HashSet<>(); + String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_LOGIN + " < ?"; + if (!includeEntriesWithLastLoginZero) { + select += " AND " + col.LAST_LOGIN + " <> 0"; + } + try (Connection con = getConnection(); + PreparedStatement selectPst = con.prepareStatement(select)) { + selectPst.setLong(1, until); + try (ResultSet rs = selectPst.executeQuery()) { + while (rs.next()) { + list.add(rs.getString(col.NAME)); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + + return list; + } + + @Override + public boolean removeAuth(String user) { + user = user.toLowerCase(); + String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + sqlExtension.removeAuth(user, con); + pst.setString(1, user.toLowerCase()); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public boolean updateQuitLoc(PlayerAuth auth) { + String sql = "UPDATE " + tableName + + " SET " + col.LASTLOC_X + " =?, " + col.LASTLOC_Y + "=?, " + col.LASTLOC_Z + "=?, " + + col.LASTLOC_WORLD + "=?, " + col.LASTLOC_YAW + "=?, " + col.LASTLOC_PITCH + "=?" + + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setDouble(1, auth.getQuitLocX()); + pst.setDouble(2, auth.getQuitLocY()); + pst.setDouble(3, auth.getQuitLocZ()); + pst.setString(4, auth.getWorld()); + pst.setFloat(5, auth.getYaw()); + pst.setFloat(6, auth.getPitch()); + pst.setString(7, auth.getNickname()); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public boolean updateEmail(PlayerAuth auth) { + String sql = "UPDATE " + tableName + " SET " + col.EMAIL + " =? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, auth.getEmail()); + pst.setString(2, auth.getNickname()); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public void closeConnection() { + if (ds != null && !ds.isClosed()) { + ds.close(); + } + } + + @Override + public List getAllAuthsByIp(String ip) { + List result = new ArrayList<>(); + String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, ip); + try (ResultSet rs = pst.executeQuery()) { + while (rs.next()) { + result.add(rs.getString(col.NAME)); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return result; + } + + @Override + public int countAuthsByEmail(String email) { + String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE UPPER(" + col.EMAIL + ") = UPPER(?)"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, email); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + return rs.getInt(1); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return 0; + } + + @Override + public void purgeRecords(Collection toPurge) { + String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + for (String name : toPurge) { + pst.setString(1, name.toLowerCase()); + pst.executeUpdate(); + } + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public abstract DataSourceType getType(); + + @Override + public boolean isLogged(String user) { + String sql = "SELECT " + col.IS_LOGGED + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user); + try (ResultSet rs = pst.executeQuery()) { + return rs.next() && (rs.getInt(col.IS_LOGGED) == 1); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public void setLogged(String user) { + String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setInt(1, 1); + pst.setString(2, user.toLowerCase()); + pst.executeUpdate(); + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public void setUnlogged(String user) { + String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setInt(1, 0); + pst.setString(2, user.toLowerCase()); + pst.executeUpdate(); + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public void purgeLogged() { + String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setInt(1, 0); + pst.setInt(2, 1); + pst.executeUpdate(); + } catch (SQLException ex) { + logSqlException(ex); + } + } + + @Override + public int getAccountsRegistered() { + int result = 0; + String sql = "SELECT COUNT(*) FROM " + tableName; + try (Connection con = getConnection(); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery(sql)) { + if (rs.next()) { + result = rs.getInt(1); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return result; + } + + @Override + public boolean updateRealName(String user, String realName) { + String sql = "UPDATE " + tableName + " SET " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, realName); + pst.setString(2, user); + pst.executeUpdate(); + return true; + } catch (SQLException ex) { + logSqlException(ex); + } + return false; + } + + @Override + public DataSourceResult getEmail(String user) { + String sql = "SELECT " + col.EMAIL + " FROM " + tableName + " WHERE " + col.NAME + "=?;"; + try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) { + pst.setString(1, user); + try (ResultSet rs = pst.executeQuery()) { + if (rs.next()) { + return DataSourceResult.of(rs.getString(1)); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return DataSourceResult.unknownPlayer(); + } + + @Override + public List getAllAuths() { + List auths = new ArrayList<>(); + try (Connection con = getConnection(); Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery("SELECT * FROM " + tableName)) { + while (rs.next()) { + PlayerAuth auth = buildAuthFromResultSet(rs); + sqlExtension.extendAuth(auth, rs.getInt(col.ID), con); + auths.add(auth); + } + } + } catch (SQLException ex) { + logSqlException(ex); + } + return auths; + } + + @Override + public List getLoggedPlayersWithEmptyMail() { + List players = new ArrayList<>(); + String sql = "SELECT " + col.REAL_NAME + " FROM " + tableName + " WHERE " + col.IS_LOGGED + " = 1" + + " AND (" + col.EMAIL + " = 'your@email.com' OR " + col.EMAIL + " IS NULL);"; + try (Connection con = getConnection(); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery(sql)) { + while (rs.next()) { + players.add(rs.getString(1)); + } + } catch (SQLException ex) { + logSqlException(ex); + } + return players; + } + + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { + String salt = col.SALT.isEmpty() ? null : row.getString(col.SALT); + int group = col.GROUP.isEmpty() ? -1 : row.getInt(col.GROUP); + return PlayerAuth.builder() + .name(row.getString(col.NAME)) + .realName(row.getString(col.REAL_NAME)) + .password(row.getString(col.PASSWORD), salt) + .lastLogin(row.getLong(col.LAST_LOGIN)) + .ip(row.getString(col.IP)) + .locWorld(row.getString(col.LASTLOC_WORLD)) + .locX(row.getDouble(col.LASTLOC_X)) + .locY(row.getDouble(col.LASTLOC_Y)) + .locZ(row.getDouble(col.LASTLOC_Z)) + .locYaw(row.getFloat(col.LASTLOC_YAW)) + .locPitch(row.getFloat(col.LASTLOC_PITCH)) + .email(row.getString(col.EMAIL)) + .groupId(group) + .build(); + } + + /** + * Checks if the last login column has a type that needs to be migrated. + * + * @param con connection to the database + * @param metaData lastlogin column meta data + * + * @throws SQLException . + */ + private void migrateLastLoginColumn(Connection con, DatabaseMetaData metaData) throws SQLException { + final int columnType; + try (ResultSet rs = metaData.getColumns(null, null, tableName, col.LAST_LOGIN)) { + if (!rs.next()) { + ConsoleLogger.warning("Could not get LAST_LOGIN meta data. This should never happen!"); + return; + } + columnType = rs.getInt("DATA_TYPE"); + } + + if (columnType == Types.TIMESTAMP) { + migrateLastLoginColumnFromTimestamp(con); + } else if (columnType == Types.INTEGER) { + migrateLastLoginColumnFromInt(con); + } + } + + /** + * Performs conversion of lastlogin column from timestamp type to bigint. + * + * @param con connection to the database + */ + private void migrateLastLoginColumnFromTimestamp(Connection con) throws SQLException { + ConsoleLogger.info("Migrating lastlogin column from timestamp to bigint"); + final String lastLoginOld = col.LAST_LOGIN + "_old"; + + // Rename lastlogin to lastlogin_old + String sql = String.format("ALTER TABLE %s CHANGE COLUMN %s %s BIGINT", + tableName, col.LAST_LOGIN, lastLoginOld); + try (PreparedStatement pst = con.prepareStatement(sql)) { + pst.execute(); + } + + // Create lastlogin column + sql = String.format("ALTER TABLE %s ADD COLUMN %s " + + "BIGINT NOT NULL DEFAULT 0 AFTER %s", + tableName, col.LAST_LOGIN, col.IP); + con.prepareStatement(sql).execute(); + + // Set values of lastlogin based on lastlogin_old + sql = String.format("UPDATE %s SET %s = UNIX_TIMESTAMP(%s) * 1000", + tableName, col.LAST_LOGIN, lastLoginOld); + con.prepareStatement(sql).execute(); + + // Drop lastlogin_old + sql = String.format("ALTER TABLE %s DROP COLUMN %s", + tableName, lastLoginOld); + con.prepareStatement(sql).execute(); + ConsoleLogger.info("Finished migration of lastlogin (timestamp to bigint)"); + } + + /** + * Performs conversion of lastlogin column from int to bigint. + * + * @param con connection to the database + */ + private void migrateLastLoginColumnFromInt(Connection con) throws SQLException { + // Change from int to bigint + ConsoleLogger.info("Migrating lastlogin column from int to bigint"); + String sql = String.format("ALTER TABLE %s MODIFY %s BIGINT;", tableName, col.LAST_LOGIN); + con.prepareStatement(sql).execute(); + + // Migrate timestamps in seconds format to milliseconds format if they are plausible + int rangeStart = 1262304000; // timestamp for 2010-01-01 + int rangeEnd = 1514678400; // timestamp for 2017-12-31 + sql = String.format("UPDATE %s SET %s = %s * 1000 WHERE %s > %d AND %s < %d;", + tableName, col.LAST_LOGIN, col.LAST_LOGIN, col.LAST_LOGIN, rangeStart, col.LAST_LOGIN, rangeEnd); + int changedRows = con.prepareStatement(sql).executeUpdate(); + + ConsoleLogger.warning("You may have entries with invalid timestamps. Please check your data " + + "before purging. " + changedRows + " rows were migrated from seconds to milliseconds."); + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java b/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java index b1a485b97..2bc588f27 100644 --- a/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java +++ b/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java @@ -3,7 +3,7 @@ package fr.xephi.authme.datasource.converter; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.datasource.MySQL; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; import fr.xephi.authme.settings.Settings; import javax.inject.Inject; @@ -15,10 +15,10 @@ import java.sql.SQLException; public class MySqlToSqlite extends AbstractDataSourceConverter { private final Settings settings; - private final MySqlExtensionsFactory mySqlExtensionsFactory; + private final SqlExtensionsFactory mySqlExtensionsFactory; @Inject - MySqlToSqlite(DataSource dataSource, Settings settings, MySqlExtensionsFactory mySqlExtensionsFactory) { + MySqlToSqlite(DataSource dataSource, Settings settings, SqlExtensionsFactory mySqlExtensionsFactory) { super(dataSource, DataSourceType.SQLITE); this.settings = settings; this.mySqlExtensionsFactory = mySqlExtensionsFactory; diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/Ipb4Extension.java similarity index 95% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/Ipb4Extension.java index 2f39b8f7d..724066606 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/Ipb4Extension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.Columns; @@ -12,7 +12,7 @@ import java.sql.SQLException; /** * Extension for IPB4. */ -class Ipb4Extension extends MySqlExtension { +class Ipb4Extension extends SqlExtension { private final String ipbPrefix; private final int ipbGroup; diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtension.java similarity index 71% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtension.java index 6d15f837b..cd777e8c5 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.settings.Settings; @@ -6,7 +6,7 @@ import fr.xephi.authme.settings.Settings; /** * Extension implementation that does not do anything. */ -class NoOpExtension extends MySqlExtension { +class NoOpExtension extends SqlExtension { NoOpExtension(Settings settings, Columns col) { super(settings, col); diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtension.java similarity index 97% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtension.java index d78aded15..b502ff984 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.Columns; @@ -13,7 +13,7 @@ import java.util.OptionalInt; /** * Extensions for phpBB when MySQL is used as data source. */ -class PhpBbExtension extends MySqlExtension { +class PhpBbExtension extends SqlExtension { private final String phpBbPrefix; private final int phpBbGroup; diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtension.java similarity index 95% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtension.java index 07374dfbd..96b5ca860 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.Columns; @@ -16,12 +16,12 @@ import java.util.OptionalInt; * Extension for the MySQL data source for forums. For certain password hashes (e.g. phpBB), we want * to hook into the forum board and execute some actions specific to the forum software. */ -public abstract class MySqlExtension { +public abstract class SqlExtension { protected final Columns col; protected final String tableName; - MySqlExtension(Settings settings, Columns col) { + SqlExtension(Settings settings, Columns col) { this.col = col; this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); } diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtensionsFactory.java similarity index 73% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtensionsFactory.java index dda94d784..a94b2b42e 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/SqlExtensionsFactory.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.security.HashAlgorithm; @@ -8,20 +8,20 @@ import fr.xephi.authme.settings.properties.SecuritySettings; import javax.inject.Inject; /** - * Creates the appropriate {@link MySqlExtension}, depending on the configured password hashing algorithm. + * Creates the appropriate {@link SqlExtension}, depending on the configured password hashing algorithm. */ -public class MySqlExtensionsFactory { +public class SqlExtensionsFactory { @Inject private Settings settings; /** - * Creates a new {@link MySqlExtension} object according to the configured hash algorithm. + * Creates a new {@link SqlExtension} object according to the configured hash algorithm. * * @param columnsConfig the columns configuration * @return the extension the MySQL data source should use */ - public MySqlExtension buildExtension(Columns columnsConfig) { + public SqlExtension buildExtension(Columns columnsConfig) { HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH); switch (hash) { case IPB4: diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtension.java similarity index 97% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtension.java index 22ef97f3c..482ac1080 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.Columns; @@ -13,7 +13,7 @@ import java.util.OptionalInt; /** * MySQL extensions for Wordpress. */ -class WordpressExtension extends MySqlExtension { +class WordpressExtension extends SqlExtension { private final String wordpressPrefix; diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java b/src/main/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtension.java similarity index 98% rename from src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java rename to src/main/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtension.java index 46dd41ef9..c3fe5618c 100644 --- a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java +++ b/src/main/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtension.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.datasource.Columns; @@ -17,7 +17,7 @@ import java.util.OptionalInt; /** * Extension for XFBCRYPT. */ -class XfBcryptExtension extends MySqlExtension { +class XfBcryptExtension extends SqlExtension { private final String xfPrefix; private final int xfGroup; diff --git a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java index 4f5d3d4dd..e748b37ba 100644 --- a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java +++ b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java @@ -6,10 +6,11 @@ import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSourceType; import fr.xephi.authme.datasource.FlatFile; +import fr.xephi.authme.datasource.MsSQL; import fr.xephi.authme.datasource.MySQL; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.datasource.converter.ForceFlatToSqlite; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; @@ -38,7 +39,7 @@ public class DataSourceProvider implements Provider { @Inject private PlayerCache playerCache; @Inject - private MySqlExtensionsFactory mySqlExtensionsFactory; + private SqlExtensionsFactory mySqlExtensionsFactory; DataSourceProvider() { } @@ -72,6 +73,9 @@ public class DataSourceProvider implements Provider { case MYSQL: dataSource = new MySQL(settings, mySqlExtensionsFactory); break; + case MSSQL: + dataSource = new MsSQL(settings, mySqlExtensionsFactory); + break; case SQLITE: dataSource = new SQLite(settings); break; diff --git a/src/main/java/fr/xephi/authme/service/BackupService.java b/src/main/java/fr/xephi/authme/service/BackupService.java index 0141e8f6e..8b1f0eea4 100644 --- a/src/main/java/fr/xephi/authme/service/BackupService.java +++ b/src/main/java/fr/xephi/authme/service/BackupService.java @@ -90,6 +90,8 @@ public class BackupService { return performFileBackup("auths.db"); case MYSQL: return performMySqlBackup(); + case MSSQL: + ConsoleLogger.warning("You have to do an SQL Server backup manually!"); case SQLITE: String dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE); return performFileBackup(dbName + ".db"); diff --git a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java index 651295164..ef6b68895 100644 --- a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java @@ -5,7 +5,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import fr.xephi.authme.datasource.Columns; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; +import fr.xephi.authme.datasource.SqlDataSource; +import fr.xephi.authme.datasource.sqlextensions.SqlExtension; import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.security.crypts.Whirlpool; @@ -56,8 +57,9 @@ public class ClassesConsistencyTest { /** Classes excluded from the field visibility test. */ private static final Set> CLASSES_EXCLUDED_FROM_VISIBILITY_TEST = ImmutableSet.of( Whirlpool.class, // not our implementation, so we don't touch it - MySqlExtension.class, // has immutable protected fields used by all children - Columns.class // uses non-static String constants, which is safe + SqlExtension.class, // has immutable protected fields used by all children + Columns.class, // uses non-static String constants, which is safe + SqlDataSource.class // has protected fields used by all children ); /** diff --git a/src/test/java/fr/xephi/authme/datasource/MySqlIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/MySqlIntegrationTest.java index 9da39a3a6..b09a9942d 100644 --- a/src/test/java/fr/xephi/authme/datasource/MySqlIntegrationTest.java +++ b/src/test/java/fr/xephi/authme/datasource/MySqlIntegrationTest.java @@ -4,8 +4,8 @@ import ch.jalu.configme.properties.Property; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.datasource.sqlextensions.SqlExtension; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; import org.junit.After; @@ -31,7 +31,7 @@ public class MySqlIntegrationTest extends AbstractDataSourceIntegrationTest { /** Mock of a settings instance. */ private static Settings settings; /** Mock of extensions factory. */ - private static MySqlExtensionsFactory extensionsFactory; + private static SqlExtensionsFactory extensionsFactory; /** SQL statement to execute before running a test. */ private static String sqlInitialize; /** Connection to the H2 test database. */ @@ -47,8 +47,8 @@ public class MySqlIntegrationTest extends AbstractDataSourceIntegrationTest { settings = mock(Settings.class); TestHelper.returnDefaultsForAllProperties(settings); - extensionsFactory = mock(MySqlExtensionsFactory.class); - when(extensionsFactory.buildExtension(any(Columns.class))).thenReturn(mock(MySqlExtension.class)); + extensionsFactory = mock(SqlExtensionsFactory.class); + when(extensionsFactory.buildExtension(any(Columns.class))).thenReturn(mock(SqlExtension.class)); set(DatabaseSettings.MYSQL_DATABASE, "h2_test"); set(DatabaseSettings.MYSQL_TABLE, "authme"); TestHelper.setRealLogger(); diff --git a/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java index 3500560b5..961fc3bd0 100644 --- a/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java @@ -1,8 +1,8 @@ package fr.xephi.authme.datasource; import com.zaxxer.hikari.HikariDataSource; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; -import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; +import fr.xephi.authme.datasource.sqlextensions.SqlExtension; +import fr.xephi.authme.datasource.sqlextensions.SqlExtensionsFactory; import fr.xephi.authme.settings.Settings; import java.lang.reflect.Method; @@ -25,8 +25,8 @@ public class MySqlResourceClosingTest extends AbstractSqlDataSourceResourceClosi protected DataSource createDataSource(Settings settings, Connection connection) throws Exception { HikariDataSource hikariDataSource = mock(HikariDataSource.class); given(hikariDataSource.getConnection()).willReturn(connection); - MySqlExtensionsFactory extensionsFactory = mock(MySqlExtensionsFactory.class); - given(extensionsFactory.buildExtension(any(Columns.class))).willReturn(mock(MySqlExtension.class)); + SqlExtensionsFactory extensionsFactory = mock(SqlExtensionsFactory.class); + given(extensionsFactory.buildExtension(any(Columns.class))).willReturn(mock(SqlExtension.class)); return new MySQL(settings, hikariDataSource, extensionsFactory); } diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/AbstractSqlExtensionResourceClosingTest.java similarity index 67% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/AbstractSqlExtensionResourceClosingTest.java index 3089e8b99..0e7c39231 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/AbstractSqlExtensionResourceClosingTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.TestHelper; import fr.xephi.authme.datasource.AbstractResourceClosingTest; @@ -16,14 +16,14 @@ import java.util.stream.Collectors; import static org.mockito.Mockito.mock; /** - * Checks that SQL resources are closed properly in {@link MySqlExtension} implementations. + * Checks that SQL resources are closed properly in {@link SqlExtension} implementations. */ -public abstract class AbstractMySqlExtensionResourceClosingTest extends AbstractResourceClosingTest { +public abstract class AbstractSqlExtensionResourceClosingTest extends AbstractResourceClosingTest { private static Settings settings; private static Columns columns; - public AbstractMySqlExtensionResourceClosingTest(Method method, String name) { + public AbstractSqlExtensionResourceClosingTest(Method method, String name) { super(method, name); } @@ -35,15 +35,15 @@ public abstract class AbstractMySqlExtensionResourceClosingTest extends Abstract } @Override - protected MySqlExtension getObjectUnderTest() { + protected SqlExtension getObjectUnderTest() { return createExtension(settings, columns); } - protected abstract MySqlExtension createExtension(Settings settings, Columns columns); + protected abstract SqlExtension createExtension(Settings settings, Columns columns); @Parameterized.Parameters(name = "{1}") public static List createParameters() { - return Arrays.stream(MySqlExtension.class.getDeclaredMethods()) + return Arrays.stream(SqlExtension.class.getDeclaredMethods()) .filter(m -> Modifier.isPublic(m.getModifiers())) .map(m -> new Object[]{m, m.getName()}) .collect(Collectors.toList()); diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/Ipb4ExtensionResourceClosingTest.java similarity index 61% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/Ipb4ExtensionResourceClosingTest.java index 8629a6a7d..4d39c2642 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/Ipb4ExtensionResourceClosingTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.settings.Settings; @@ -8,14 +8,14 @@ import java.lang.reflect.Method; /** * Resource closing test for {@link Ipb4Extension}. */ -public class Ipb4ExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { +public class Ipb4ExtensionResourceClosingTest extends AbstractSqlExtensionResourceClosingTest { public Ipb4ExtensionResourceClosingTest(Method method, String name) { super(method, name); } @Override - protected MySqlExtension createExtension(Settings settings, Columns columns) { + protected SqlExtension createExtension(Settings settings, Columns columns) { return new Ipb4Extension(settings, columns); } } diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtensionTest.java similarity index 96% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtensionTest.java index 95a5dfa88..554b643f6 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/NoOpExtensionTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtensionResourceClosingTest.java similarity index 61% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtensionResourceClosingTest.java index d1f43e1f4..c4969e371 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/PhpBbExtensionResourceClosingTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.settings.Settings; @@ -8,14 +8,14 @@ import java.lang.reflect.Method; /** * Resource closing test for {@link PhpBbExtension}. */ -public class PhpBbExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { +public class PhpBbExtensionResourceClosingTest extends AbstractSqlExtensionResourceClosingTest { public PhpBbExtensionResourceClosingTest(Method method, String name) { super(method, name); } @Override - protected MySqlExtension createExtension(Settings settings, Columns columns) { + protected SqlExtension createExtension(Settings settings, Columns columns) { return new PhpBbExtension(settings, columns); } } diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtensionResourceClosingTest.java similarity index 71% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtensionResourceClosingTest.java index 1ee141ffe..62e163413 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/WordpressExtensionResourceClosingTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.settings.Settings; @@ -8,14 +8,14 @@ import java.lang.reflect.Method; /** * Resource closing test for {@link WordpressExtension}. */ -public class WordpressExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { +public class WordpressExtensionResourceClosingTest extends AbstractSqlExtensionResourceClosingTest { public WordpressExtensionResourceClosingTest(Method method, String name) { super(method, name); } @Override - protected MySqlExtension createExtension(Settings settings, Columns columns) { + protected SqlExtension createExtension(Settings settings, Columns columns) { return new WordpressExtension(settings, columns); } } diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtensionResourceClosingTest.java similarity index 71% rename from src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java rename to src/test/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtensionResourceClosingTest.java index 7da4fb198..e844d61f5 100644 --- a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/sqlextensions/XfBcryptExtensionResourceClosingTest.java @@ -1,4 +1,4 @@ -package fr.xephi.authme.datasource.mysqlextensions; +package fr.xephi.authme.datasource.sqlextensions; import fr.xephi.authme.datasource.Columns; import fr.xephi.authme.settings.Settings; @@ -8,14 +8,14 @@ import java.lang.reflect.Method; /** * Resource closing test for {@link XfBcryptExtension}. */ -public class XfBcryptExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { +public class XfBcryptExtensionResourceClosingTest extends AbstractSqlExtensionResourceClosingTest { public XfBcryptExtensionResourceClosingTest(Method method, String name) { super(method, name); } @Override - protected MySqlExtension createExtension(Settings settings, Columns columns) { + protected SqlExtension createExtension(Settings settings, Columns columns) { return new XfBcryptExtension(settings, columns); } }