diff --git a/pom.xml b/pom.xml
index 2177188d..b872eb4c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
fr.xephiauthme
- 5.3.3-SNAPSHOT
+ 5.4-SNAPSHOTAuthMeReloadedThe first authentication plugin for the Bukkit API!
diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index b86ad716..73f75840 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -5,17 +5,15 @@ 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.security.HashAlgorithm;
+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.security.crypts.XfBCrypt;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.HooksSettings;
-import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
-import java.sql.Blob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
@@ -44,19 +42,11 @@ public class MySQL implements DataSource {
private int maxLifetime;
private List columnOthers;
private Columns col;
- private HashAlgorithm hashAlgorithm;
+ private MySqlExtension sqlExtension;
private HikariDataSource ds;
- private String phpBbPrefix;
- private String ipbPrefix;
- private String xfPrefix;
- private String wordpressPrefix;
- private int phpBbGroup;
- private int ipbGroup;
- private int xfGroup;
-
- public MySQL(Settings settings) throws ClassNotFoundException, SQLException {
- setParameters(settings);
+ public MySQL(Settings settings, MySqlExtensionsFactory extensionsFactory) throws SQLException {
+ setParameters(settings, extensionsFactory);
// Set the connection arguments (and check if connection is ok)
try {
@@ -86,17 +76,18 @@ public class MySQL implements DataSource {
}
@VisibleForTesting
- MySQL(Settings settings, HikariDataSource hikariDataSource) {
+ MySQL(Settings settings, HikariDataSource hikariDataSource, MySqlExtensionsFactory extensionsFactory) {
ds = hikariDataSource;
- setParameters(settings);
+ setParameters(settings, 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) {
+ 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);
@@ -105,14 +96,7 @@ public class MySQL implements DataSource {
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS);
this.col = new Columns(settings);
- this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
- this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
- this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID);
- this.ipbPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX);
- this.ipbGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID);
- this.xfPrefix = settings.getProperty(HooksSettings.XF_TABLE_PREFIX);
- this.xfGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID);
- this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
+ sqlExtension = extensionsFactory.buildExtension(col);
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
if (poolSize == -1) {
poolSize = Utils.getCoreCount() * 3;
@@ -303,28 +287,14 @@ public class MySQL implements DataSource {
PlayerAuth auth;
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, user.toLowerCase());
- int id;
try (ResultSet rs = pst.executeQuery()) {
- if (!rs.next()) {
- return null;
- }
- id = rs.getInt(col.ID);
- auth = buildAuthFromResultSet(rs);
- }
- if (hashAlgorithm == HashAlgorithm.XFBCRYPT) {
- try (PreparedStatement pst2 = con.prepareStatement(
- "SELECT data FROM " + xfPrefix + "user_authenticate WHERE " + col.ID + "=?;")) {
- pst2.setInt(1, id);
- try (ResultSet rs = pst2.executeQuery()) {
- if (rs.next()) {
- Blob blob = rs.getBlob("data");
- byte[] bytes = blob.getBytes(1, (int) blob.length());
- auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
- }
- }
+ if (rs.next()) {
+ int id = rs.getInt(col.ID);
+ auth = buildAuthFromResultSet(rs);
+ sqlExtension.extendAuth(auth, id, con);
+ return auth;
}
}
- return auth;
} catch (SQLException ex) {
logSqlException(ex);
}
@@ -364,231 +334,8 @@ public class MySQL implements DataSource {
}
}
}
- if (hashAlgorithm == HashAlgorithm.IPB4) {
- sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst = con.prepareStatement(sql)) {
- pst.setString(1, auth.getNickname());
- try (ResultSet rs = pst.executeQuery()) {
- if (rs.next()) {
- // Update player group in core_members
- sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".member_group_id=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, ipbGroup);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Get current time without ms
- long time = System.currentTimeMillis() / 1000;
- // update joined date
- sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".joined=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setLong(1, time);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Update last_visit
- sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setLong(1, time);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- }
- }
- }
- } else if (hashAlgorithm == HashAlgorithm.PHPBB) {
- sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst = con.prepareStatement(sql)) {
- pst.setString(1, auth.getNickname());
- try (ResultSet rs = pst.executeQuery()) {
- if (rs.next()) {
- int id = rs.getInt(col.ID);
- // Insert player in phpbb_user_group
- sql = "INSERT INTO " + phpBbPrefix
- + "user_group (group_id, user_id, group_leader, user_pending) VALUES (?,?,?,?);";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, phpBbGroup);
- pst2.setInt(2, id);
- pst2.setInt(3, 0);
- pst2.setInt(4, 0);
- pst2.executeUpdate();
- }
- // Update username_clean in phpbb_users
- sql = "UPDATE " + tableName + " SET " + tableName
- + ".username_clean=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setString(1, auth.getNickname());
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Update player group in phpbb_users
- sql = "UPDATE " + tableName + " SET " + tableName
- + ".group_id=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, phpBbGroup);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Get current time without ms
- long time = System.currentTimeMillis() / 1000;
- // Update user_regdate
- sql = "UPDATE " + tableName + " SET " + tableName
- + ".user_regdate=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setLong(1, time);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Update user_lastvisit
- sql = "UPDATE " + tableName + " SET " + tableName
- + ".user_lastvisit=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setLong(1, time);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Increment num_users
- sql = "UPDATE " + phpBbPrefix
- + "config SET config_value = config_value + 1 WHERE config_name = 'num_users';";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.executeUpdate();
- }
- }
- }
- }
- } else if (hashAlgorithm == HashAlgorithm.WORDPRESS) {
- // NOTE: Eclipse says pst should be closed HERE, but it's a bug, we already close it above. -sgdc3
- try (PreparedStatement pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;")) {
- pst.setString(1, auth.getNickname());
- try (ResultSet rs = pst.executeQuery()) {
- if (rs.next()) {
- int id = rs.getInt(col.ID);
- sql = "INSERT INTO " + wordpressPrefix + "usermeta (user_id, meta_key, meta_value) VALUES (?,?,?)";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- // First Name
- pst2.setInt(1, id);
- pst2.setString(2, "first_name");
- pst2.setString(3, "");
- pst2.addBatch();
- // Last Name
- pst2.setInt(1, id);
- pst2.setString(2, "last_name");
- pst2.setString(3, "");
- pst2.addBatch();
- // Nick Name
- pst2.setInt(1, id);
- pst2.setString(2, "nickname");
- pst2.setString(3, auth.getNickname());
- pst2.addBatch();
- // Description
- pst2.setInt(1, id);
- pst2.setString(2, "description");
- pst2.setString(3, "");
- pst2.addBatch();
- // Rich_Editing
- pst2.setInt(1, id);
- pst2.setString(2, "rich_editing");
- pst2.setString(3, "true");
- pst2.addBatch();
- // Comments_Shortcuts
- pst2.setInt(1, id);
- pst2.setString(2, "comment_shortcuts");
- pst2.setString(3, "false");
- pst2.addBatch();
- // admin_color
- pst2.setInt(1, id);
- pst2.setString(2, "admin_color");
- pst2.setString(3, "fresh");
- pst2.addBatch();
- // use_ssl
- pst2.setInt(1, id);
- pst2.setString(2, "use_ssl");
- pst2.setString(3, "0");
- pst2.addBatch();
- // show_admin_bar_front
- pst2.setInt(1, id);
- pst2.setString(2, "show_admin_bar_front");
- pst2.setString(3, "true");
- pst2.addBatch();
- // wp_capabilities
- pst2.setInt(1, id);
- pst2.setString(2, wordpressPrefix + "capabilities");
- pst2.setString(3, "a:1:{s:10:\"subscriber\";b:1;}");
- pst2.addBatch();
- // wp_user_level
- pst2.setInt(1, id);
- pst2.setString(2, wordpressPrefix + "user_level");
- pst2.setString(3, "0");
- pst2.addBatch();
- // default_password_nag
- pst2.setInt(1, id);
- pst2.setString(2, "default_password_nag");
- pst2.setString(3, "");
- pst2.addBatch();
- // Execute queries
- pst2.executeBatch();
- pst2.clearBatch();
- }
- }
- }
- }
- } else if (hashAlgorithm == HashAlgorithm.XFBCRYPT) {
- // NOTE: Eclipse says pst should be closed HERE, but it's a bug, we already close it above. -sgdc3
- try (PreparedStatement pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;")) {
- pst.setString(1, auth.getNickname());
- try (ResultSet rs = pst.executeQuery()) {
- if (rs.next()) {
- int id = rs.getInt(col.ID);
- // Insert player password, salt in xf_user_authenticate
- sql = "INSERT INTO " + xfPrefix + "user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, id);
- pst2.setString(2, XfBCrypt.SCHEME_CLASS);
- String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash());
- byte[] bytes = serializedHash.getBytes();
- Blob blob = con.createBlob();
- blob.setBytes(1, bytes);
- pst2.setBlob(3, blob);
- pst2.executeUpdate();
- }
- // Update player group in xf_users
- sql = "UPDATE " + tableName + " SET " + tableName + ".user_group_id=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, xfGroup);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Update player permission combination in xf_users
- sql = "UPDATE " + tableName + " SET " + tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, xfGroup);
- pst2.setString(2, auth.getNickname());
- pst2.executeUpdate();
- }
- // Insert player privacy combination in xf_user_privacy
- sql = "INSERT INTO " + xfPrefix + "user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, id);
- pst2.setString(2, "everyone");
- pst2.setString(3, "members");
- pst2.setString(4, "members");
- pst2.setString(5, "everyone");
- pst2.setString(6, "everyone");
- pst2.executeUpdate();
- }
- // Insert player group relation in xf_user_group_relation
- sql = "INSERT INTO " + xfPrefix + "user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)";
- try (PreparedStatement pst2 = con.prepareStatement(sql)) {
- pst2.setInt(1, id);
- pst2.setInt(2, xfGroup);
- pst2.setString(3, "1");
- pst2.executeUpdate();
- }
- }
- }
- }
- }
+ sqlExtension.saveAuth(auth, con);
return true;
} catch (SQLException ex) {
logSqlException(ex);
@@ -624,35 +371,7 @@ public class MySQL implements DataSource {
pst.executeUpdate();
}
}
- if (hashAlgorithm == HashAlgorithm.XFBCRYPT) {
- String sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
- try (PreparedStatement pst = con.prepareStatement(sql)) {
- pst.setString(1, user);
- try (ResultSet rs = pst.executeQuery()) {
- if (rs.next()) {
- int id = rs.getInt(col.ID);
- // Insert password in the correct table
- sql = "UPDATE " + xfPrefix + "user_authenticate SET data=? WHERE " + col.ID + "=?;";
- PreparedStatement pst2 = con.prepareStatement(sql);
- String serializedHash = XfBCrypt.serializeHash(password.getHash());
- byte[] bytes = serializedHash.getBytes();
- Blob blob = con.createBlob();
- blob.setBytes(1, bytes);
- pst2.setBlob(1, blob);
- pst2.setInt(2, id);
- pst2.executeUpdate();
- pst2.close();
- // ...
- sql = "UPDATE " + xfPrefix + "user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;";
- pst2 = con.prepareStatement(sql);
- pst2.setString(1, XfBCrypt.SCHEME_CLASS);
- pst2.setInt(2, id);
- pst2.executeUpdate();
- pst2.close();
- }
- }
- }
- }
+ sqlExtension.changePassword(user, password, con);
return true;
} catch (SQLException ex) {
logSqlException(ex);
@@ -704,22 +423,7 @@ public class MySQL implements DataSource {
user = user.toLowerCase();
String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;";
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
- if (hashAlgorithm == HashAlgorithm.XFBCRYPT) {
- sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
- try (PreparedStatement xfSelect = con.prepareStatement(sql)) {
- xfSelect.setString(1, user);
- try (ResultSet rs = xfSelect.executeQuery()) {
- if (rs.next()) {
- int id = rs.getInt(col.ID);
- sql = "DELETE FROM " + xfPrefix + "user_authenticate WHERE " + col.ID + "=?;";
- try (PreparedStatement xfDelete = con.prepareStatement(sql)) {
- xfDelete.setInt(1, id);
- xfDelete.executeUpdate();
- }
- }
- }
- }
- }
+ sqlExtension.removeAuth(user, con);
pst.setString(1, user.toLowerCase());
pst.executeUpdate();
return true;
@@ -732,8 +436,8 @@ public class MySQL implements DataSource {
@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 + "=?"
+ + " 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());
@@ -922,26 +626,12 @@ public class MySQL implements DataSource {
@Override
public List getAllAuths() {
List auths = new ArrayList<>();
- try (Connection con = getConnection()) {
- try (Statement st = con.createStatement()) {
- try (ResultSet rs = st.executeQuery("SELECT * FROM " + tableName)) {
- while (rs.next()) {
- PlayerAuth pAuth = buildAuthFromResultSet(rs);
- if (hashAlgorithm == HashAlgorithm.XFBCRYPT) {
- try (PreparedStatement pst = con.prepareStatement("SELECT data FROM " + xfPrefix + "user_authenticate WHERE " + col.ID + "=?;")) {
- int id = rs.getInt(col.ID);
- pst.setInt(1, id);
- ResultSet rs2 = pst.executeQuery();
- if (rs2.next()) {
- Blob blob = rs2.getBlob("data");
- byte[] bytes = blob.getBytes(1, (int) blob.length());
- pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
- }
- rs2.close();
- }
- }
- auths.add(pAuth);
- }
+ 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) {
@@ -987,36 +677,6 @@ public class MySQL implements DataSource {
.build();
}
- /**
- * Closes a {@link ResultSet} safely.
- *
- * @param rs the result set to close
- */
- private static void close(ResultSet rs) {
- try {
- if (rs != null && !rs.isClosed()) {
- rs.close();
- }
- } catch (SQLException e) {
- ConsoleLogger.logException("Could not close ResultSet", e);
- }
- }
-
- /**
- * Closes a {@link Statement} safely.
- *
- * @param st the statement set to close
- */
- private static void close(Statement st) {
- try {
- if (st != null && !st.isClosed()) {
- st.close();
- }
- } catch (SQLException e) {
- ConsoleLogger.logException("Could not close Statement", e);
- }
- }
-
/**
* Checks if the last login column has a type that needs to be migrated.
*
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 7b793e8b..b1a485b9 100644
--- a/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java
+++ b/src/main/java/fr/xephi/authme/datasource/converter/MySqlToSqlite.java
@@ -3,6 +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.settings.Settings;
import javax.inject.Inject;
@@ -14,15 +15,17 @@ import java.sql.SQLException;
public class MySqlToSqlite extends AbstractDataSourceConverter {
private final Settings settings;
+ private final MySqlExtensionsFactory mySqlExtensionsFactory;
@Inject
- MySqlToSqlite(DataSource dataSource, Settings settings) {
+ MySqlToSqlite(DataSource dataSource, Settings settings, MySqlExtensionsFactory mySqlExtensionsFactory) {
super(dataSource, DataSourceType.SQLITE);
this.settings = settings;
+ this.mySqlExtensionsFactory = mySqlExtensionsFactory;
}
@Override
- protected MySQL getSource() throws SQLException, ClassNotFoundException {
- return new MySQL(settings);
+ protected MySQL getSource() throws SQLException {
+ return new MySQL(settings, mySqlExtensionsFactory);
}
}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java
new file mode 100644
index 00000000..2f39b8f7
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4Extension.java
@@ -0,0 +1,55 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.HooksSettings;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Extension for IPB4.
+ */
+class Ipb4Extension extends MySqlExtension {
+
+ private final String ipbPrefix;
+ private final int ipbGroup;
+
+ Ipb4Extension(Settings settings, Columns col) {
+ super(settings, col);
+ this.ipbPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX);
+ this.ipbGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID);
+ }
+
+ @Override
+ public void saveAuth(PlayerAuth auth, Connection con) throws SQLException {
+ // Update player group in core_members
+ String sql = "UPDATE " + ipbPrefix + tableName
+ + " SET " + tableName + ".member_group_id=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst2 = con.prepareStatement(sql)) {
+ pst2.setInt(1, ipbGroup);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ }
+ // Get current time without ms
+ long time = System.currentTimeMillis() / 1000;
+ // update joined date
+ sql = "UPDATE " + ipbPrefix + tableName
+ + " SET " + tableName + ".joined=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst2 = con.prepareStatement(sql)) {
+ pst2.setLong(1, time);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ }
+ // Update last_visit
+ sql = "UPDATE " + ipbPrefix + tableName
+ + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst2 = con.prepareStatement(sql)) {
+ pst2.setLong(1, time);
+ pst2.setString(2, auth.getNickname());
+ pst2.executeUpdate();
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java
new file mode 100644
index 00000000..07374dfb
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtension.java
@@ -0,0 +1,96 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.DatabaseSettings;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+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 {
+
+ protected final Columns col;
+ protected final String tableName;
+
+ MySqlExtension(Settings settings, Columns col) {
+ this.col = col;
+ this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
+ }
+
+ /**
+ * Performs additional actions when a new player is saved.
+ *
+ * @param auth the player auth that has been saved
+ * @param con connection to the sql table
+ * @throws SQLException .
+ */
+ public void saveAuth(PlayerAuth auth, Connection con) throws SQLException {
+ // extend for custom behavior
+ }
+
+ /**
+ * Writes properties to the given PlayerAuth object that need to be retrieved in a specific manner
+ * when a PlayerAuth object is read from the table.
+ *
+ * @param auth the player auth object to extend
+ * @param id the database id of the player auth entry
+ * @param con connection to the sql table
+ * @throws SQLException .
+ */
+ public void extendAuth(PlayerAuth auth, int id, Connection con) throws SQLException {
+ // extend for custom behavior
+ }
+
+ /**
+ * Performs additional actions when a user's password is changed.
+ *
+ * @param user the name of the player (lowercase)
+ * @param password the new password to set
+ * @param con connection to the sql table
+ * @throws SQLException .
+ */
+ public void changePassword(String user, HashedPassword password, Connection con) throws SQLException {
+ // extend for custom behavior
+ }
+
+ /**
+ * Performs additional actions when a player is removed from the database.
+ *
+ * @param user the user to remove
+ * @param con connection to the sql table
+ * @throws SQLException .
+ */
+ public void removeAuth(String user, Connection con) throws SQLException {
+ // extend for custom behavior
+ }
+
+ /**
+ * Fetches the database ID of the given name from the database.
+ *
+ * @param name the name to get the ID for
+ * @param con connection to the sql table
+ * @return id of the playerAuth, or empty OptionalInt if the name is not registered
+ * @throws SQLException .
+ */
+ protected OptionalInt retrieveIdFromTable(String name, Connection con) throws SQLException {
+ String sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setString(1, name);
+ try (ResultSet rs = pst.executeQuery()) {
+ if (rs.next()) {
+ return OptionalInt.of(rs.getInt(col.ID));
+ }
+ }
+ }
+ return OptionalInt.empty();
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java
new file mode 100644
index 00000000..dda94d78
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/MySqlExtensionsFactory.java
@@ -0,0 +1,39 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.security.HashAlgorithm;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.SecuritySettings;
+
+import javax.inject.Inject;
+
+/**
+ * Creates the appropriate {@link MySqlExtension}, depending on the configured password hashing algorithm.
+ */
+public class MySqlExtensionsFactory {
+
+ @Inject
+ private Settings settings;
+
+ /**
+ * Creates a new {@link MySqlExtension} 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) {
+ HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH);
+ switch (hash) {
+ case IPB4:
+ return new Ipb4Extension(settings, columnsConfig);
+ case PHPBB:
+ return new PhpBbExtension(settings, columnsConfig);
+ case WORDPRESS:
+ return new WordpressExtension(settings, columnsConfig);
+ case XFBCRYPT:
+ return new XfBcryptExtension(settings, columnsConfig);
+ default:
+ return new NoOpExtension(settings, columnsConfig);
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java
new file mode 100644
index 00000000..6d15f837
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtension.java
@@ -0,0 +1,14 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.settings.Settings;
+
+/**
+ * Extension implementation that does not do anything.
+ */
+class NoOpExtension extends MySqlExtension {
+
+ 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/mysqlextensions/PhpBbExtension.java
new file mode 100644
index 00000000..d78aded1
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtension.java
@@ -0,0 +1,83 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.HooksSettings;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.OptionalInt;
+
+/**
+ * Extensions for phpBB when MySQL is used as data source.
+ */
+class PhpBbExtension extends MySqlExtension {
+
+ private final String phpBbPrefix;
+ private final int phpBbGroup;
+
+ PhpBbExtension(Settings settings, Columns col) {
+ super(settings, col);
+ this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
+ this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID);
+ }
+
+ @Override
+ public void saveAuth(PlayerAuth auth, Connection con) throws SQLException {
+ OptionalInt authId = retrieveIdFromTable(auth.getNickname(), con);
+ if (authId.isPresent()) {
+ updateSpecificsOnSave(authId.getAsInt(), auth.getNickname(), con);
+ }
+ }
+
+ private void updateSpecificsOnSave(int id, String name, Connection con) throws SQLException {
+ // Insert player in phpbb_user_group
+ String sql = "INSERT INTO " + phpBbPrefix
+ + "user_group (group_id, user_id, group_leader, user_pending) VALUES (?,?,?,?);";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, phpBbGroup);
+ pst.setInt(2, id);
+ pst.setInt(3, 0);
+ pst.setInt(4, 0);
+ pst.executeUpdate();
+ }
+ // Update username_clean in phpbb_users
+ sql = "UPDATE " + tableName + " SET " + tableName + ".username_clean=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setString(1, name);
+ pst.setString(2, name);
+ pst.executeUpdate();
+ }
+ // Update player group in phpbb_users
+ sql = "UPDATE " + tableName + " SET " + tableName + ".group_id=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, phpBbGroup);
+ pst.setString(2, name);
+ pst.executeUpdate();
+ }
+ // Get current time without ms
+ long time = System.currentTimeMillis() / 1000;
+ // Update user_regdate
+ sql = "UPDATE " + tableName + " SET " + tableName + ".user_regdate=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setLong(1, time);
+ pst.setString(2, name);
+ pst.executeUpdate();
+ }
+ // Update user_lastvisit
+ sql = "UPDATE " + tableName + " SET " + tableName + ".user_lastvisit=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setLong(1, time);
+ pst.setString(2, name);
+ pst.executeUpdate();
+ }
+ // Increment num_users
+ sql = "UPDATE " + phpBbPrefix
+ + "config SET config_value = config_value + 1 WHERE config_name = 'num_users';";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.executeUpdate();
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java
new file mode 100644
index 00000000..22ef97f3
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtension.java
@@ -0,0 +1,102 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.HooksSettings;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.OptionalInt;
+
+/**
+ * MySQL extensions for Wordpress.
+ */
+class WordpressExtension extends MySqlExtension {
+
+ private final String wordpressPrefix;
+
+ WordpressExtension(Settings settings, Columns col) {
+ super(settings, col);
+ this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
+ }
+
+ @Override
+ public void saveAuth(PlayerAuth auth, Connection con) throws SQLException {
+ OptionalInt authId = retrieveIdFromTable(auth.getNickname(), con);
+ if (authId.isPresent()) {
+ saveSpecifics(auth, authId.getAsInt(), con);
+ }
+ }
+
+ private void saveSpecifics(PlayerAuth auth, int id, Connection con) throws SQLException {
+ String sql = "INSERT INTO " + wordpressPrefix + "usermeta (user_id, meta_key, meta_value) VALUES (?,?,?)";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ // First Name
+ pst.setInt(1, id);
+ pst.setString(2, "first_name");
+ pst.setString(3, "");
+ pst.addBatch();
+ // Last Name
+ pst.setInt(1, id);
+ pst.setString(2, "last_name");
+ pst.setString(3, "");
+ pst.addBatch();
+ // Nick Name
+ pst.setInt(1, id);
+ pst.setString(2, "nickname");
+ pst.setString(3, auth.getNickname());
+ pst.addBatch();
+ // Description
+ pst.setInt(1, id);
+ pst.setString(2, "description");
+ pst.setString(3, "");
+ pst.addBatch();
+ // Rich_Editing
+ pst.setInt(1, id);
+ pst.setString(2, "rich_editing");
+ pst.setString(3, "true");
+ pst.addBatch();
+ // Comments_Shortcuts
+ pst.setInt(1, id);
+ pst.setString(2, "comment_shortcuts");
+ pst.setString(3, "false");
+ pst.addBatch();
+ // admin_color
+ pst.setInt(1, id);
+ pst.setString(2, "admin_color");
+ pst.setString(3, "fresh");
+ pst.addBatch();
+ // use_ssl
+ pst.setInt(1, id);
+ pst.setString(2, "use_ssl");
+ pst.setString(3, "0");
+ pst.addBatch();
+ // show_admin_bar_front
+ pst.setInt(1, id);
+ pst.setString(2, "show_admin_bar_front");
+ pst.setString(3, "true");
+ pst.addBatch();
+ // wp_capabilities
+ pst.setInt(1, id);
+ pst.setString(2, wordpressPrefix + "capabilities");
+ pst.setString(3, "a:1:{s:10:\"subscriber\";b:1;}");
+ pst.addBatch();
+ // wp_user_level
+ pst.setInt(1, id);
+ pst.setString(2, wordpressPrefix + "user_level");
+ pst.setString(3, "0");
+ pst.addBatch();
+ // default_password_nag
+ pst.setInt(1, id);
+ pst.setString(2, "default_password_nag");
+ pst.setString(3, "");
+ pst.addBatch();
+
+ // Execute queries
+ pst.executeBatch();
+ pst.clearBatch();
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java
new file mode 100644
index 00000000..46dd41ef
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtension.java
@@ -0,0 +1,140 @@
+package fr.xephi.authme.datasource.mysqlextensions;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.Columns;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.security.crypts.XfBCrypt;
+import fr.xephi.authme.settings.Settings;
+import fr.xephi.authme.settings.properties.HooksSettings;
+
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.OptionalInt;
+
+/**
+ * Extension for XFBCRYPT.
+ */
+class XfBcryptExtension extends MySqlExtension {
+
+ private final String xfPrefix;
+ private final int xfGroup;
+
+ XfBcryptExtension(Settings settings, Columns col) {
+ super(settings, col);
+ this.xfPrefix = settings.getProperty(HooksSettings.XF_TABLE_PREFIX);
+ this.xfGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID);
+ }
+
+ @Override
+ public void saveAuth(PlayerAuth auth, Connection con) throws SQLException {
+ OptionalInt authId = retrieveIdFromTable(auth.getNickname(), con);
+ if (authId.isPresent()) {
+ updateXenforoTablesOnSave(auth, authId.getAsInt(), con);
+ }
+ }
+
+ private void updateXenforoTablesOnSave(PlayerAuth auth, int id, Connection con) throws SQLException {
+ // Insert player password, salt in xf_user_authenticate
+ String sql = "INSERT INTO " + xfPrefix + "user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, id);
+ pst.setString(2, XfBCrypt.SCHEME_CLASS);
+ String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash());
+ byte[] bytes = serializedHash.getBytes();
+ Blob blob = con.createBlob();
+ blob.setBytes(1, bytes);
+ pst.setBlob(3, blob);
+ pst.executeUpdate();
+ }
+ // Update player group in xf_users
+ sql = "UPDATE " + tableName + " SET " + tableName + ".user_group_id=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, xfGroup);
+ pst.setString(2, auth.getNickname());
+ pst.executeUpdate();
+ }
+ // Update player permission combination in xf_users
+ sql = "UPDATE " + tableName + " SET " + tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, xfGroup);
+ pst.setString(2, auth.getNickname());
+ pst.executeUpdate();
+ }
+ // Insert player privacy combination in xf_user_privacy
+ sql = "INSERT INTO " + xfPrefix + "user_privacy (user_id, allow_view_profile, allow_post_profile, "
+ + "allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, id);
+ pst.setString(2, "everyone");
+ pst.setString(3, "members");
+ pst.setString(4, "members");
+ pst.setString(5, "everyone");
+ pst.setString(6, "everyone");
+ pst.executeUpdate();
+ }
+ // Insert player group relation in xf_user_group_relation
+ sql = "INSERT INTO " + xfPrefix + "user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setInt(1, id);
+ pst.setInt(2, xfGroup);
+ pst.setString(3, "1");
+ pst.executeUpdate();
+ }
+ }
+
+ public void extendAuth(PlayerAuth auth, int id, Connection con) throws SQLException {
+ try (PreparedStatement pst = con.prepareStatement(
+ "SELECT data FROM " + xfPrefix + "user_authenticate WHERE " + col.ID + "=?;")) {
+ pst.setInt(1, id);
+ try (ResultSet rs = pst.executeQuery()) {
+ if (rs.next()) {
+ Blob blob = rs.getBlob("data");
+ byte[] bytes = blob.getBytes(1, (int) blob.length());
+ auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void changePassword(String user, HashedPassword password, Connection con) throws SQLException {
+ OptionalInt authId = retrieveIdFromTable(user, con);
+ if (authId.isPresent()) {
+ final int id = authId.getAsInt();
+ // Insert password in the correct table
+ String sql = "UPDATE " + xfPrefix + "user_authenticate SET data=? WHERE " + col.ID + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ String serializedHash = XfBCrypt.serializeHash(password.getHash());
+ byte[] bytes = serializedHash.getBytes();
+ Blob blob = con.createBlob();
+ blob.setBytes(1, bytes);
+ pst.setBlob(1, blob);
+ pst.setInt(2, id);
+ pst.executeUpdate();
+ }
+
+ // ...
+ sql = "UPDATE " + xfPrefix + "user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;";
+ try (PreparedStatement pst = con.prepareStatement(sql)) {
+ pst.setString(1, XfBCrypt.SCHEME_CLASS);
+ pst.setInt(2, id);
+ pst.executeUpdate();
+ }
+ }
+ }
+
+ @Override
+ public void removeAuth(String user, Connection con) throws SQLException {
+ OptionalInt authId = retrieveIdFromTable(user, con);
+ if (authId.isPresent()) {
+ String sql = "DELETE FROM " + xfPrefix + "user_authenticate WHERE " + col.ID + "=?;";
+ try (PreparedStatement xfDelete = con.prepareStatement(sql)) {
+ xfDelete.setInt(1, authId.getAsInt());
+ xfDelete.executeUpdate();
+ }
+ }
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java
index 594cabf4..4f5d3d4d 100644
--- a/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java
+++ b/src/main/java/fr/xephi/authme/initialization/DataSourceProvider.java
@@ -9,6 +9,7 @@ import fr.xephi.authme.datasource.FlatFile;
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.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
@@ -36,6 +37,8 @@ public class DataSourceProvider implements Provider {
private BukkitService bukkitService;
@Inject
private PlayerCache playerCache;
+ @Inject
+ private MySqlExtensionsFactory mySqlExtensionsFactory;
DataSourceProvider() {
}
@@ -67,7 +70,7 @@ public class DataSourceProvider implements Provider {
dataSource = new FlatFile(source);
break;
case MYSQL:
- dataSource = new MySQL(settings);
+ dataSource = new MySQL(settings, mySqlExtensionsFactory);
break;
case SQLITE:
dataSource = new SQLite(settings);
@@ -82,12 +85,12 @@ public class DataSourceProvider implements Provider {
dataSource = new CacheDataSource(dataSource, playerCache);
}
if (DataSourceType.SQLITE.equals(dataSourceType)) {
- checkDataSourceSize(dataSource, bukkitService);
+ checkDataSourceSize(dataSource);
}
return dataSource;
}
- private void checkDataSourceSize(final DataSource dataSource, BukkitService bukkitService) {
+ private void checkDataSourceSize(DataSource dataSource) {
bukkitService.runTaskAsynchronously(() -> {
int accounts = dataSource.getAccountsRegistered();
if (accounts >= SQLITE_MAX_SIZE) {
diff --git a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
index a8b3a0fa..65129516 100644
--- a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
+++ b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java
@@ -5,6 +5,7 @@ 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.initialization.HasCleanup;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.security.crypts.Whirlpool;
@@ -55,6 +56,7 @@ 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
);
diff --git a/src/test/java/fr/xephi/authme/TestHelper.java b/src/test/java/fr/xephi/authme/TestHelper.java
index ee7aaa5e..5a60bc1e 100644
--- a/src/test/java/fr/xephi/authme/TestHelper.java
+++ b/src/test/java/fr/xephi/authme/TestHelper.java
@@ -1,6 +1,8 @@
package fr.xephi.authme;
+import ch.jalu.configme.properties.Property;
import fr.xephi.authme.service.BukkitService;
+import fr.xephi.authme.settings.Settings;
import org.bukkit.entity.Player;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -18,6 +20,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -206,4 +209,14 @@ public final class TestHelper {
given(player.getAddress()).willReturn(inetSocketAddress);
}
+ /**
+ * Configures the Settings mock to return the property's default value for any given property.
+ *
+ * @param settings the settings mock
+ */
+ @SuppressWarnings("unchecked")
+ public static void returnDefaultsForAllProperties(Settings settings) {
+ given(settings.getProperty(any(Property.class)))
+ .willAnswer(invocation -> ((Property>) invocation.getArgument(0)).getDefaultValue());
+ }
}
diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java
index 1da098e1..5b2dca58 100644
--- a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java
+++ b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java
@@ -1,17 +1,10 @@
package fr.xephi.authme.datasource;
-import ch.jalu.configme.properties.Property;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.auth.PlayerAuth;
-import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.HashedPassword;
-import fr.xephi.authme.settings.Settings;
-import fr.xephi.authme.settings.properties.SecuritySettings;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -19,7 +12,6 @@ import org.junit.runners.Parameterized;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
@@ -37,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -46,124 +37,57 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
- * Test class which runs through a datasource implementation and verifies that all
+ * Test class which runs through objects interacting with a database and verifies that all
* instances of {@link AutoCloseable} that are created in the calls are closed again.
*
* Instead of an actual connection to a datasource, we pass a mock Connection object
* which is set to create additional mocks on demand for Statement and ResultSet objects.
* This test ensures that all such objects that are created will be closed again by
* keeping a list of mocks ({@link #closeables}) and then verifying that all have been
- * closed {@link #verifyHaveMocksBeenClosed()}.
+ * closed ({@link #verifyHaveMocksBeenClosed()}).
*/
@RunWith(Parameterized.class)
public abstract class AbstractResourceClosingTest {
- /** List of DataSource method names not to test. */
- private static final Set IGNORED_METHODS = ImmutableSet.of("reload", "close", "getType");
-
/** Collection of values to use to call methods with the parameters they expect. */
private static final Map, Object> PARAM_VALUES = getDefaultParameters();
- /**
- * Custom list of hash algorithms to use to test a method. By default we define {@link HashAlgorithm#XFBCRYPT} as
- * algorithms we use as a lot of methods execute additional statements in {@link MySQL}. If other algorithms
- * have custom behaviors, they can be supplied in this map so it will be tested as well.
- */
- private static final Map CUSTOM_ALGORITHMS = getCustomAlgorithmList();
-
- /** Mock of a settings instance. */
- private static Settings settings;
-
- /** The datasource to test. */
- private DataSource dataSource;
-
/** The DataSource method to test. */
private Method method;
/** Keeps track of the closeables which are created during the tested call. */
private List closeables = new ArrayList<>();
+ private boolean hasCreatedConnection = false;
+
/**
- * Constructor for the test instance verifying the given method with the given hash algorithm.
+ * Constructor for the test instance verifying the given method.
*
* @param method The DataSource method to test
* @param name The name of the method
- * @param algorithm The hash algorithm to use
*/
- public AbstractResourceClosingTest(Method method, String name, HashAlgorithm algorithm) {
+ public AbstractResourceClosingTest(Method method, String name) {
// Note ljacqu 20160227: The name parameter is necessary as we pass it from the @Parameters method;
// we use the method name in the annotation to name the test sensibly
this.method = method;
- given(settings.getProperty(SecuritySettings.PASSWORD_HASH)).willReturn(algorithm);
}
- /** Initialize the settings mock and makes it return the default of any given property by default. */
- @SuppressWarnings({ "unchecked", "rawtypes" })
@BeforeClass
- public static void initializeSettings() throws IOException, ClassNotFoundException {
- settings = mock(Settings.class);
- given(settings.getProperty(any(Property.class))).willAnswer(new Answer() {
- @Override
- public Object answer(InvocationOnMock invocation) {
- return ((Property>) invocation.getArguments()[0]).getDefaultValue();
- }
- });
+ public static void initializeLogger() {
TestHelper.setupLogger();
}
- /** Initialize the dataSource implementation to test based on a mock connection. */
- @Before
- public void setUpMockConnection() throws Exception {
- Connection connection = initConnection();
- dataSource = createDataSource(settings, connection);
- }
-
/**
* The actual test -- executes the method given through the constructor and then verifies that all
* AutoCloseable mocks it constructed have been closed.
*/
@Test
public void shouldCloseResources() throws IllegalAccessException, InvocationTargetException {
- method.invoke(dataSource, buildParamListForMethod(method));
+ method.invoke(getObjectUnderTest(), buildParamListForMethod(method));
verifyHaveMocksBeenClosed();
}
- /**
- * Initialization method -- provides the parameters to run the test with by scanning all DataSource
- * methods. By default, we run one test per method with the default hash algorithm, XFBCRYPT.
- * If the map of custom algorithms has an entry for the method name, we add an entry for each algorithm
- * supplied by the map.
- *
- * @return Test parameters
- */
- @Parameterized.Parameters(name = "{1}({2})")
- public static Collection