- Old SQLite setups have the last IP column as NOT NULL but without a default value. With the new concept (where we don't set a last IP on player registration) it fails. - Create an /authme debug child that allows to migrate SQLite (tricky because SQLite does not support dropping or modifying columns) - Allow last IP column to be NOT NULL in MySQL as well (extend MySQL /authme debug child) - Add TODO comments with follow-up issue to extend our commands with new registration IP field
This commit is contained in:
parent
b5ea48085c
commit
1651a61063
@ -27,6 +27,7 @@ public class AccountsCommand implements ExecutableCommand {
|
||||
|
||||
@Override
|
||||
public void executeCommand(final CommandSender sender, List<String> arguments) {
|
||||
// TODO #1366: last IP vs. registration IP?
|
||||
final String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
|
||||
|
||||
// Assumption: a player name cannot contain '.'
|
||||
@ -52,6 +53,9 @@ public class AccountsCommand implements ExecutableCommand {
|
||||
if (auth == null) {
|
||||
commonService.send(sender, MessageKey.UNKNOWN_USER);
|
||||
return;
|
||||
} else if (auth.getLastIp() == null) {
|
||||
sender.sendMessage("No known last IP address for player");
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> accountList = dataSource.getAllAuthsByIp(auth.getLastIp());
|
||||
|
||||
@ -72,10 +72,13 @@ class CountryLookup implements DebugSection {
|
||||
sender.sendMessage("Note: if " + ProtectionSettings.ENABLE_PROTECTION + " is false no country is blocked");
|
||||
}
|
||||
|
||||
// TODO #1366: Extend with registration IP?
|
||||
private void outputInfoForPlayer(CommandSender sender, String name) {
|
||||
PlayerAuth auth = dataSource.getAuth(name);
|
||||
if (auth == null) {
|
||||
sender.sendMessage("No player with name '" + name + "'");
|
||||
} else if (auth.getLastIp() == null) {
|
||||
sender.sendMessage("No last IP address known for '" + name + "'");
|
||||
} else {
|
||||
sender.sendMessage("Player '" + name + "' has IP address " + auth.getLastIp());
|
||||
outputInfoForIpAddr(sender, auth.getLastIp());
|
||||
|
||||
@ -21,7 +21,7 @@ public class DebugCommand implements ExecutableCommand {
|
||||
private static final Set<Class<? extends DebugSection>> SECTION_CLASSES = ImmutableSet.of(
|
||||
PermissionGroups.class, DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, InputValidator.class,
|
||||
LimboPlayerViewer.class, CountryLookup.class, HasPermissionChecker.class, TestEmailSender.class,
|
||||
SpawnLocationViewer.class, MySqlDefaultChanger.class);
|
||||
SpawnLocationViewer.class, MySqlDefaultChanger.class, SqliteMigrater.class);
|
||||
|
||||
@Inject
|
||||
private Factory<DebugSection> debugSectionFactory;
|
||||
|
||||
@ -2,6 +2,8 @@ package fr.xephi.authme.command.executable.authme.debug;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.datasource.CacheDataSource;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -85,7 +87,7 @@ final class DebugSectionUtils {
|
||||
* @param function the function to apply to the map
|
||||
* @param <U> the result type of the function
|
||||
*
|
||||
* @return player names for which there is a LimboPlayer (or error message upon failure)
|
||||
* @return the value of the function applied to the map, or null upon error
|
||||
*/
|
||||
static <U> U applyToLimboPlayersMap(LimboService limboService, Function<Map, U> function) {
|
||||
Field limboPlayerEntriesField = getLimboPlayerEntriesField();
|
||||
@ -98,4 +100,29 @@ final class DebugSectionUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static <T> T castToTypeOrNull(Object object, Class<T> clazz) {
|
||||
return clazz.isInstance(object) ? clazz.cast(object) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps the "cache data source" and returns the underlying source. Returns the
|
||||
* same as the input argument otherwise.
|
||||
*
|
||||
* @param dataSource the data source to unwrap if applicable
|
||||
* @return the non-cache data source
|
||||
*/
|
||||
static DataSource unwrapSourceFromCacheDataSource(DataSource dataSource) {
|
||||
if (dataSource instanceof CacheDataSource) {
|
||||
try {
|
||||
Field source = CacheDataSource.class.getDeclaredField("source");
|
||||
source.setAccessible(true);
|
||||
return (DataSource) source.get(dataSource);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
ConsoleLogger.logException("Could not get source of CacheDataSource:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package fr.xephi.authme.command.executable.authme.debug;
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.datasource.CacheDataSource;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.datasource.MySQL;
|
||||
import fr.xephi.authme.permission.DebugSectionPermissions;
|
||||
@ -15,21 +14,23 @@ import org.bukkit.command.CommandSender;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.castToTypeOrNull;
|
||||
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.unwrapSourceFromCacheDataSource;
|
||||
import static fr.xephi.authme.data.auth.PlayerAuth.DB_EMAIL_DEFAULT;
|
||||
import static fr.xephi.authme.data.auth.PlayerAuth.DB_LAST_IP_DEFAULT;
|
||||
import static fr.xephi.authme.data.auth.PlayerAuth.DB_LAST_LOGIN_DEFAULT;
|
||||
import static fr.xephi.authme.datasource.SqlDataSourceUtils.isNotNullColumn;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
@ -48,10 +49,7 @@ class MySqlDefaultChanger implements DebugSection {
|
||||
|
||||
@PostConstruct
|
||||
void setMySqlField() {
|
||||
DataSource dataSource = unwrapSourceFromCacheDataSource(this.dataSource);
|
||||
if (dataSource instanceof MySQL) {
|
||||
this.mySql = (MySQL) dataSource;
|
||||
}
|
||||
this.mySql = castToTypeOrNull(unwrapSourceFromCacheDataSource(this.dataSource), MySQL.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -213,24 +211,6 @@ class MySqlDefaultChanger implements DebugSection {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotNullColumn(DatabaseMetaData metaData, String tableName,
|
||||
String columnName) throws SQLException {
|
||||
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException("Did not find meta data for column '" + columnName
|
||||
+ "' while migrating not-null columns (this should never happen!)");
|
||||
}
|
||||
|
||||
int nullableCode = rs.getInt("NULLABLE");
|
||||
if (nullableCode == DatabaseMetaData.columnNoNulls) {
|
||||
return true;
|
||||
} else if (nullableCode == DatabaseMetaData.columnNullableUnknown) {
|
||||
ConsoleLogger.warning("Unknown nullable status for column '" + columnName + "'");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Connection object from the MySQL data source.
|
||||
*
|
||||
@ -248,28 +228,6 @@ class MySqlDefaultChanger implements DebugSection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps the "cache data source" and returns the underlying source. Returns the
|
||||
* same as the input argument otherwise.
|
||||
*
|
||||
* @param dataSource the data source to unwrap if applicable
|
||||
* @return the non-cache data source
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static DataSource unwrapSourceFromCacheDataSource(DataSource dataSource) {
|
||||
if (dataSource instanceof CacheDataSource) {
|
||||
try {
|
||||
Field source = CacheDataSource.class.getDeclaredField("source");
|
||||
source.setAccessible(true);
|
||||
return (DataSource) source.get(dataSource);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
ConsoleLogger.logException("Could not get source of CacheDataSource:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> E matchToEnum(List<String> arguments, int index, Class<E> clazz) {
|
||||
if (arguments.size() <= index) {
|
||||
return null;
|
||||
@ -290,6 +248,11 @@ class MySqlDefaultChanger implements DebugSection {
|
||||
LASTLOGIN(DatabaseSettings.MYSQL_COL_LASTLOGIN,
|
||||
"BIGINT", "BIGINT NOT NULL DEFAULT 0", DB_LAST_LOGIN_DEFAULT),
|
||||
|
||||
LASTIP(DatabaseSettings.MYSQL_COL_LAST_IP,
|
||||
"VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin",
|
||||
"VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '127.0.0.1'",
|
||||
DB_LAST_IP_DEFAULT),
|
||||
|
||||
EMAIL(DatabaseSettings.MYSQL_COL_EMAIL,
|
||||
"VARCHAR(255)", "VARCHAR(255) NOT NULL DEFAULT 'your@email.com'", DB_EMAIL_DEFAULT);
|
||||
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
package fr.xephi.authme.command.executable.authme.debug;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.datasource.Columns;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.datasource.SQLite;
|
||||
import fr.xephi.authme.permission.DebugSectionPermissions;
|
||||
import fr.xephi.authme.permission.PermissionNode;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.castToTypeOrNull;
|
||||
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.unwrapSourceFromCacheDataSource;
|
||||
import static org.bukkit.ChatColor.BOLD;
|
||||
import static org.bukkit.ChatColor.GOLD;
|
||||
|
||||
/**
|
||||
* Performs a migration on the SQLite data source if necessary.
|
||||
*/
|
||||
class SqliteMigrater implements DebugSection {
|
||||
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
|
||||
@Inject
|
||||
private Settings settings;
|
||||
|
||||
private SQLite sqLite;
|
||||
|
||||
private String confirmationCode;
|
||||
|
||||
@PostConstruct
|
||||
void setSqLiteField() {
|
||||
this.sqLite = castToTypeOrNull(unwrapSourceFromCacheDataSource(this.dataSource), SQLite.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "migratesqlite";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Migrates the SQLite database";
|
||||
}
|
||||
|
||||
// A migration can be forced even if SQLite says it doesn't need a migration by adding "force" as second argument
|
||||
@Override
|
||||
public void execute(CommandSender sender, List<String> arguments) {
|
||||
if (sqLite == null) {
|
||||
sender.sendMessage("This command migrates SQLite. You are currently not using a SQLite database.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMigrationRequired() && !isMigrationForced(arguments)) {
|
||||
sender.sendMessage("Good news! No migration is required of your database");
|
||||
} else if (checkConfirmationCodeAndInformSenderOnMismatch(sender, arguments)) {
|
||||
final String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
|
||||
final Columns columns = new Columns(settings);
|
||||
try {
|
||||
recreateDatabaseWithNewDefinitions(tableName, columns);
|
||||
sender.sendMessage(ChatColor.GREEN + "Successfully migrated your SQLite database!");
|
||||
} catch (SQLException e) {
|
||||
ConsoleLogger.logException("Failed to migrate SQLite database", e);
|
||||
sender.sendMessage(ChatColor.RED
|
||||
+ "An error occurred during SQLite migration. Please check the logs!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkConfirmationCodeAndInformSenderOnMismatch(CommandSender sender, List<String> arguments) {
|
||||
boolean isMatch = !arguments.isEmpty() && arguments.get(0).equalsIgnoreCase(confirmationCode);
|
||||
if (isMatch) {
|
||||
confirmationCode = null;
|
||||
return true;
|
||||
} else {
|
||||
confirmationCode = RandomStringUtils.generate(4).toUpperCase();
|
||||
sender.sendMessage(new String[]{
|
||||
BOLD.toString() + GOLD + "Please create a backup of your SQLite database before running this command!",
|
||||
"Either copy your DB file or run /authme backup. Afterwards,",
|
||||
String.format("run '/authme debug %s %s' to perform the migration. "
|
||||
+ "The code confirms that you've made a backup!", getName(), confirmationCode)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionNode getRequiredPermission() {
|
||||
return DebugSectionPermissions.MIGRATE_SQLITE;
|
||||
}
|
||||
|
||||
private boolean isMigrationRequired() {
|
||||
Connection connection = getConnection(sqLite);
|
||||
try {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
return sqLite.isMigrationRequired(metaData);
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException("Could not check if SQLite migration is required", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMigrationForced(List<String> arguments) {
|
||||
return arguments.size() >= 2 && "force".equals(arguments.get(1));
|
||||
}
|
||||
|
||||
// Cannot rename or remove a column from SQLite, so we have to rename the table and create an updated one
|
||||
// cf. https://stackoverflow.com/questions/805363/how-do-i-rename-a-column-in-a-sqlite-database-table
|
||||
private void recreateDatabaseWithNewDefinitions(String tableName, Columns col) throws SQLException {
|
||||
Connection connection = getConnection(sqLite);
|
||||
String tempTable = "tmp_" + tableName;
|
||||
try (Statement st = connection.createStatement()) {
|
||||
st.execute("ALTER TABLE " + tableName + " RENAME TO " + tempTable + ";");
|
||||
}
|
||||
|
||||
sqLite.reload();
|
||||
connection = getConnection(sqLite);
|
||||
|
||||
try (Statement st = connection.createStatement()) {
|
||||
String copySql = "INSERT INTO $table ($id, $name, $realName, $password, $lastIp, $lastLogin, $regIp, "
|
||||
+ "$regDate, $locX, $locY, $locZ, $locWorld, $locPitch, $locYaw, $email, $isLogged)"
|
||||
+ "SELECT $id, $name, $realName,"
|
||||
+ " $password, CASE WHEN $lastIp = '127.0.0.1' OR $lastIp = '' THEN NULL else $lastIp END,"
|
||||
+ " $lastLogin, $regIp, $regDate, $locX, $locY, $locZ, $locWorld, $locPitch, $locYaw,"
|
||||
+ " CASE WHEN $email = 'your@email.com' THEN NULL ELSE $email END, $isLogged"
|
||||
+ " FROM " + tempTable + ";";
|
||||
int insertedEntries = st.executeUpdate(replaceColumnVariables(copySql, tableName, col));
|
||||
ConsoleLogger.info("Copied over " + insertedEntries + " from the old table to the new one");
|
||||
|
||||
st.execute("DROP TABLE " + tempTable + ";");
|
||||
}
|
||||
}
|
||||
|
||||
private String replaceColumnVariables(String sql, String tableName, Columns col) {
|
||||
String replacedSql = sql.replace("$table", tableName).replace("$id", col.ID)
|
||||
.replace("$name", col.NAME).replace("$realName", col.REAL_NAME)
|
||||
.replace("$password", col.PASSWORD).replace("$lastIp", col.LAST_IP)
|
||||
.replace("$lastLogin", col.LAST_LOGIN).replace("$regIp", col.REGISTRATION_IP)
|
||||
.replace("$regDate", col.REGISTRATION_DATE).replace("$locX", col.LASTLOC_X)
|
||||
.replace("$locY", col.LASTLOC_Y).replace("$locZ", col.LASTLOC_Z)
|
||||
.replace("$locWorld", col.LASTLOC_WORLD).replace("$locPitch", col.LASTLOC_PITCH)
|
||||
.replace("$locYaw", col.LASTLOC_YAW).replace("$email", col.EMAIL)
|
||||
.replace("$isLogged", col.IS_LOGGED);
|
||||
if (replacedSql.contains("$")) {
|
||||
throw new IllegalStateException("SQL still statement still has '$' in it - was a tag not replaced?"
|
||||
+ " Replacement result: " + replacedSql);
|
||||
}
|
||||
return replacedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection from the given SQLite instance.
|
||||
*
|
||||
* @param sqLite the SQLite instance to process
|
||||
* @return the connection to the SQLite database
|
||||
*/
|
||||
private static Connection getConnection(SQLite sqLite) {
|
||||
try {
|
||||
Field connectionField = SQLite.class.getDeclaredField("con");
|
||||
connectionField.setAccessible(true);
|
||||
return (Connection) connectionField.get(sqLite);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new IllegalStateException("Failed to get the connection from SQLite", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,8 @@ public class PlayerAuth {
|
||||
public static final String DB_EMAIL_DEFAULT = "your@email.com";
|
||||
/** Default last login value used in the database if the last login column is NOT NULL. */
|
||||
public static final long DB_LAST_LOGIN_DEFAULT = 0;
|
||||
/** Default last ip value used in the database if the last IP column is NOT NULL. */
|
||||
public static final String DB_LAST_IP_DEFAULT = "127.0.0.1";
|
||||
|
||||
/** The player's name in lowercase, e.g. "xephi". */
|
||||
private String nickname;
|
||||
@ -218,7 +220,7 @@ public class PlayerAuth {
|
||||
auth.realName = firstNonNull(realName, "Player");
|
||||
auth.password = firstNonNull(password, new HashedPassword(""));
|
||||
auth.email = DB_EMAIL_DEFAULT.equals(email) ? null : email;
|
||||
auth.lastIp = firstNonNull(lastIp, "127.0.0.1");
|
||||
auth.lastIp = lastIp; // Don't check against default value 127.0.0.1 as it may be a legit value
|
||||
auth.groupId = groupId;
|
||||
auth.lastLogin = isEqualTo(lastLogin, DB_LAST_LOGIN_DEFAULT) ? null : lastLogin;
|
||||
auth.registrationIp = registrationIp;
|
||||
|
||||
@ -192,7 +192,7 @@ public class MySQL implements DataSource {
|
||||
|
||||
if (isColumnMissing(md, col.LAST_IP)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;");
|
||||
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin;");
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.LAST_LOGIN)) {
|
||||
|
||||
@ -97,7 +97,7 @@ public class SQLite implements DataSource {
|
||||
|
||||
if (isColumnMissing(md, col.LAST_IP)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) NOT NULL DEFAULT '';");
|
||||
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40);");
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.LAST_LOGIN)) {
|
||||
@ -152,10 +152,30 @@ public class SQLite implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
|
||||
}
|
||||
|
||||
if (isMigrationRequired(md)) {
|
||||
ConsoleLogger.warning("READ ME! Your SQLite database is outdated and cannot save new players.");
|
||||
ConsoleLogger.warning("Run /authme debug migratesqlite after making a backup");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("SQLite Setup finished");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the database needs to be migrated.
|
||||
* <p>
|
||||
* Background: Before commit 22911a0 (July 2016), new SQLite databases initialized the last IP column to be NOT NULL
|
||||
* without a default value. Allowing the last IP to be null (#792) is therefore not compatible.
|
||||
*
|
||||
* @param metaData the database meta data
|
||||
* @return true if a migration is necessary, false otherwise
|
||||
* @throws SQLException .
|
||||
*/
|
||||
public boolean isMigrationRequired(DatabaseMetaData metaData) throws SQLException {
|
||||
return SqlDataSourceUtils.isNotNullColumn(metaData, tableName, col.LAST_IP)
|
||||
&& SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, col.LAST_IP) == null;
|
||||
}
|
||||
|
||||
private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException {
|
||||
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
|
||||
return !rs.next();
|
||||
|
||||
@ -2,13 +2,14 @@ package fr.xephi.authme.datasource;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Utilities for SQL data sources.
|
||||
*/
|
||||
final class SqlDataSourceUtils {
|
||||
public final class SqlDataSourceUtils {
|
||||
|
||||
private SqlDataSourceUtils() {
|
||||
}
|
||||
@ -18,7 +19,7 @@ final class SqlDataSourceUtils {
|
||||
*
|
||||
* @param e the exception to log
|
||||
*/
|
||||
static void logSqlException(SQLException e) {
|
||||
public static void logSqlException(SQLException e) {
|
||||
ConsoleLogger.logException("Error during SQL operation:", e);
|
||||
}
|
||||
|
||||
@ -31,8 +32,55 @@ final class SqlDataSourceUtils {
|
||||
* @return the value (which may be null)
|
||||
* @throws SQLException :)
|
||||
*/
|
||||
static Long getNullableLong(ResultSet rs, String columnName) throws SQLException {
|
||||
public static Long getNullableLong(ResultSet rs, String columnName) throws SQLException {
|
||||
long longValue = rs.getLong(columnName);
|
||||
return rs.wasNull() ? null : longValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given column has a NOT NULL constraint.
|
||||
*
|
||||
* @param metaData the database meta data
|
||||
* @param tableName the name of the table in which the column is
|
||||
* @param columnName the name of the column to check
|
||||
* @return true if the column is NOT NULL, false otherwise
|
||||
* @throws SQLException :)
|
||||
*/
|
||||
public static boolean isNotNullColumn(DatabaseMetaData metaData, String tableName,
|
||||
String columnName) throws SQLException {
|
||||
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException("Did not find meta data for column '"
|
||||
+ columnName + "' while checking for not-null constraint");
|
||||
}
|
||||
|
||||
int nullableCode = rs.getInt("NULLABLE");
|
||||
if (nullableCode == DatabaseMetaData.columnNoNulls) {
|
||||
return true;
|
||||
} else if (nullableCode == DatabaseMetaData.columnNullableUnknown) {
|
||||
ConsoleLogger.warning("Unknown nullable status for column '" + columnName + "'");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default value of a column (as per its SQL definition).
|
||||
*
|
||||
* @param metaData the database meta data
|
||||
* @param tableName the name of the table in which the column is
|
||||
* @param columnName the name of the column to check
|
||||
* @return the default value of the column (may be null)
|
||||
* @throws SQLException :)
|
||||
*/
|
||||
public static Object getColumnDefaultValue(DatabaseMetaData metaData, String tableName,
|
||||
String columnName) throws SQLException {
|
||||
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException("Did not find meta data for column '"
|
||||
+ columnName + "' while checking its default value");
|
||||
}
|
||||
return rs.getObject("COLUMN_DEF");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,9 @@ public enum DebugSectionPermissions implements PermissionNode {
|
||||
/** Permission to change nullable status of MySQL columns. */
|
||||
MYSQL_DEFAULT_CHANGER("authme.debug.mysqldef"),
|
||||
|
||||
/** Permission to perform a migration of SQLite. */
|
||||
MIGRATE_SQLITE("authme.debug.migratesqlite"),
|
||||
|
||||
/** Permission to view spawn information. */
|
||||
SPAWN_LOCATION("authme.debug.spawn"),
|
||||
|
||||
|
||||
@ -173,6 +173,7 @@ permissions:
|
||||
authme.debug.group: true
|
||||
authme.debug.limbo: true
|
||||
authme.debug.mail: true
|
||||
authme.debug.migratesqlite: true
|
||||
authme.debug.mysqldef: true
|
||||
authme.debug.perm: true
|
||||
authme.debug.spawn: true
|
||||
@ -196,6 +197,9 @@ permissions:
|
||||
authme.debug.mail:
|
||||
description: Permission to use the test email sender.
|
||||
default: op
|
||||
authme.debug.migratesqlite:
|
||||
description: Permission to perform a migration of SQLite.
|
||||
default: op
|
||||
authme.debug.mysqldef:
|
||||
description: Permission to change nullable status of MySQL columns.
|
||||
default: op
|
||||
|
||||
@ -82,7 +82,8 @@ public class AccountsCommandTest {
|
||||
// given
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
List<String> arguments = Collections.singletonList("SomeUser");
|
||||
given(dataSource.getAuth("someuser")).willReturn(mock(PlayerAuth.class));
|
||||
PlayerAuth auth = authWithIp("144.56.77.88");
|
||||
given(dataSource.getAuth("someuser")).willReturn(auth);
|
||||
|
||||
// when
|
||||
command.executeCommand(sender, arguments);
|
||||
|
||||
@ -11,6 +11,8 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@ -20,11 +22,16 @@ import static org.junit.Assert.fail;
|
||||
public class DebugSectionConsistencyTest {
|
||||
|
||||
private static List<Class<?>> debugClasses;
|
||||
private static List<DebugSection> debugSections;
|
||||
|
||||
@BeforeClass
|
||||
public static void collectClasses() {
|
||||
debugClasses = new ClassCollector(
|
||||
TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "command/executable/authme/debug").collectClasses();
|
||||
// TODO ljacqu 20171021: Improve ClassCollector (pass pkg by class, improve #getInstancesOfType's instantiation)
|
||||
ClassCollector classCollector = new ClassCollector(
|
||||
TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT + "command/executable/authme/debug");
|
||||
|
||||
debugClasses = classCollector.collectClasses();
|
||||
debugSections = classCollector.getInstancesOfType(DebugSection.class, clz -> instantiate(clz));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -40,13 +47,26 @@ public class DebugSectionConsistencyTest {
|
||||
@Test
|
||||
public void shouldHaveDifferentSubcommandName() throws IllegalAccessException, InstantiationException {
|
||||
Set<String> names = new HashSet<>();
|
||||
for (Class<?> clazz : debugClasses) {
|
||||
if (DebugSection.class.isAssignableFrom(clazz) && !clazz.isInterface()) {
|
||||
DebugSection debugSection = (DebugSection) clazz.newInstance();
|
||||
if (!names.add(debugSection.getName())) {
|
||||
fail("Encountered name '" + debugSection.getName() + "' a second time in " + clazz);
|
||||
}
|
||||
for (DebugSection debugSection : debugSections) {
|
||||
if (!names.add(debugSection.getName())) {
|
||||
fail("Encountered name '" + debugSection.getName() + "' a second time in " + debugSection.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAllHaveDescription() {
|
||||
for (DebugSection debugSection : debugSections) {
|
||||
assertThat("Description of '" + debugSection.getClass() + "' may not be null",
|
||||
debugSection.getDescription(), not(nullValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private static DebugSection instantiate(Class<? extends DebugSection> clazz) {
|
||||
try {
|
||||
return ClassCollector.canInstantiate(clazz) ? clazz.newInstance() : null;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,11 @@ package fr.xephi.authme.command.executable.authme.debug;
|
||||
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.datasource.CacheDataSource;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import org.bukkit.Location;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -89,4 +92,39 @@ public class DebugSectionUtilsTest {
|
||||
// then
|
||||
assertThat(result, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnSameDataSourceInstance() {
|
||||
// given
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
// when
|
||||
DataSource result = DebugSectionUtils.unwrapSourceFromCacheDataSource(dataSource);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(dataSource));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUnwrapCacheDataSource() {
|
||||
// given
|
||||
DataSource source = mock(DataSource.class);
|
||||
PlayerCache playerCache = mock(PlayerCache.class);
|
||||
CacheDataSource cacheDataSource = new CacheDataSource(source, playerCache);
|
||||
|
||||
// when
|
||||
DataSource result = DebugSectionUtils.unwrapSourceFromCacheDataSource(cacheDataSource);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCastOrReturnNull() {
|
||||
// given / when / then
|
||||
assertThat(DebugSectionUtils.castToTypeOrNull("test", String.class), equalTo("test"));
|
||||
assertThat(DebugSectionUtils.castToTypeOrNull("test", Integer.class), nullValue());
|
||||
assertThat(DebugSectionUtils.castToTypeOrNull(5, String.class), nullValue());
|
||||
assertThat(DebugSectionUtils.castToTypeOrNull(5, Integer.class), equalTo(5));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.datasource.CacheDataSource;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.datasource.MySQL;
|
||||
import fr.xephi.authme.datasource.MySqlTestUtil;
|
||||
import fr.xephi.authme.datasource.SqlDataSourceTestUtil;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -34,32 +34,6 @@ public class MySqlDefaultChangerTest {
|
||||
@Mock
|
||||
private Settings settings;
|
||||
|
||||
@Test
|
||||
public void shouldReturnSameDataSourceInstance() {
|
||||
// given
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
// when
|
||||
DataSource result = MySqlDefaultChanger.unwrapSourceFromCacheDataSource(dataSource);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(dataSource));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUnwrapCacheDataSource() {
|
||||
// given
|
||||
DataSource source = mock(DataSource.class);
|
||||
PlayerCache playerCache = mock(PlayerCache.class);
|
||||
CacheDataSource cacheDataSource = new CacheDataSource(source, playerCache);
|
||||
|
||||
// when
|
||||
DataSource result = MySqlDefaultChanger.unwrapSourceFromCacheDataSource(cacheDataSource);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnMySqlConnection() throws SQLException {
|
||||
// given
|
||||
@ -68,7 +42,7 @@ public class MySqlDefaultChangerTest {
|
||||
HikariDataSource dataSource = mock(HikariDataSource.class);
|
||||
Connection connection = mock(Connection.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
MySQL mySQL = MySqlTestUtil.createMySql(settings, dataSource);
|
||||
MySQL mySQL = SqlDataSourceTestUtil.createMySql(settings, dataSource);
|
||||
MySqlDefaultChanger defaultChanger = createDefaultChanger(mySQL);
|
||||
|
||||
// when
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
package fr.xephi.authme.command.executable.authme.debug;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.SQLite;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static fr.xephi.authme.AuthMeMatchers.hasAuthBasicData;
|
||||
import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation;
|
||||
import static fr.xephi.authme.datasource.SqlDataSourceTestUtil.createSqliteAndInitialize;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Integration test for {@link SqliteMigrater}. Uses a real SQLite database.
|
||||
*/
|
||||
public class SqliteMigraterIntegrationTest {
|
||||
|
||||
private static final String CONFIRMATION_CODE = "ABCD";
|
||||
|
||||
private SqliteMigrater sqliteMigrater;
|
||||
private SQLite sqLite;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setup() throws SQLException, IOException, NoSuchMethodException {
|
||||
TestHelper.setupLogger();
|
||||
|
||||
Settings settings = mock(Settings.class);
|
||||
TestHelper.returnDefaultsForAllProperties(settings);
|
||||
|
||||
File sqliteDbFile = TestHelper.getJarFile(TestHelper.PROJECT_ROOT + "datasource/sqlite.april2016.db");
|
||||
File tempFile = temporaryFolder.newFile();
|
||||
Files.copy(sqliteDbFile, tempFile);
|
||||
|
||||
Connection con = DriverManager.getConnection("jdbc:sqlite:" + tempFile.getPath());
|
||||
sqLite = createSqliteAndInitialize(settings, con);
|
||||
|
||||
sqliteMigrater = new SqliteMigrater();
|
||||
ReflectionTestUtils.setField(sqliteMigrater, "dataSource", sqLite);
|
||||
ReflectionTestUtils.setField(sqliteMigrater, "settings", settings);
|
||||
ReflectionTestUtils.setField(sqliteMigrater, "confirmationCode", CONFIRMATION_CODE);
|
||||
sqliteMigrater.setSqLiteField();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRun() throws ClassNotFoundException, SQLException {
|
||||
// given
|
||||
CommandSender sender = mock(CommandSender.class);
|
||||
|
||||
// when
|
||||
sqliteMigrater.execute(sender, Collections.singletonList(CONFIRMATION_CODE));
|
||||
|
||||
// then
|
||||
List<PlayerAuth> auths = sqLite.getAllAuths();
|
||||
assertThat(Lists.transform(auths, PlayerAuth::getNickname),
|
||||
containsInAnyOrder("mysql1", "mysql2", "mysql3", "mysql4", "mysql5", "mysql6"));
|
||||
PlayerAuth auth1 = getByNameOrFail("mysql1", auths);
|
||||
assertThat(auth1, hasAuthBasicData("mysql1", "mysql1", "user1@example.com", "192.168.4.41"));
|
||||
assertThat(auth1, hasAuthLocation(0, 0, 0, "world1", 0, 0));
|
||||
assertThat(auth1.getLastLogin(), equalTo(1472992664137L));
|
||||
PlayerAuth auth2 = getByNameOrFail("mysql2", auths);
|
||||
assertThat(auth2, hasAuthBasicData("mysql2", "Player", "user2@example.com", null));
|
||||
assertThat(auth2, hasAuthLocation(0, 0, 0, "world2", 0, 0));
|
||||
assertThat(auth2.getLastLogin(), equalTo(1472992668391L));
|
||||
PlayerAuth auth3 = getByNameOrFail("mysql3", auths);
|
||||
assertThat(auth3, hasAuthBasicData("mysql3", "mysql3", null, "132.54.76.98"));
|
||||
assertThat(auth3, hasAuthLocation(0, 0, 0, "world3", 0, 0));
|
||||
assertThat(auth3.getLastLogin(), equalTo(1472992672790L));
|
||||
PlayerAuth auth4 = getByNameOrFail("mysql4", auths);
|
||||
assertThat(auth4, hasAuthBasicData("mysql4", "MySQL4", null, null));
|
||||
assertThat(auth4, hasAuthLocation(25, 4, 17, "world4", 0, 0));
|
||||
assertThat(auth4.getLastLogin(), equalTo(1472992676790L));
|
||||
PlayerAuth auth5 = getByNameOrFail("mysql5", auths);
|
||||
assertThat(auth5, hasAuthBasicData("mysql5", "mysql5", null, null));
|
||||
assertThat(auth5, hasAuthLocation(0, 0, 0, "world5", 0, 0));
|
||||
assertThat(auth5.getLastLogin(), equalTo(1472992680922L));
|
||||
PlayerAuth auth6 = getByNameOrFail("mysql6", auths);
|
||||
assertThat(auth6, hasAuthBasicData("mysql6", "MySql6", "user6@example.com", "44.45.67.188"));
|
||||
assertThat(auth6, hasAuthLocation(28.5, 53.43, -147.23, "world6", 0, 0));
|
||||
assertThat(auth6.getLastLogin(), equalTo(1472992686300L));
|
||||
}
|
||||
|
||||
private static PlayerAuth getByNameOrFail(String name, List<PlayerAuth> auths) {
|
||||
return auths.stream()
|
||||
.filter(auth -> name.equals(auth.getNickname()))
|
||||
.findFirst().orElseThrow(() -> new IllegalStateException("No PlayerAuth with name '" + name + "'"));
|
||||
}
|
||||
}
|
||||
@ -18,12 +18,16 @@ public class PlayerAuthTest {
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name("Bobby")
|
||||
.lastLogin(0L)
|
||||
.lastIp("127.0.0.1")
|
||||
.email("your@email.com")
|
||||
.build();
|
||||
|
||||
// then
|
||||
assertThat(auth.getNickname(), equalTo("bobby"));
|
||||
assertThat(auth.getLastLogin(), nullValue());
|
||||
// Note ljacqu 20171020: Although 127.0.0.1 is the default value, we need to keep it because it might
|
||||
// legitimately be the resolved IP of a player
|
||||
assertThat(auth.getLastIp(), equalTo("127.0.0.1"));
|
||||
assertThat(auth.getEmail(), nullValue());
|
||||
}
|
||||
|
||||
@ -50,6 +54,7 @@ public class PlayerAuthTest {
|
||||
.name("Charlie")
|
||||
.email(null)
|
||||
.lastLogin(null)
|
||||
.lastIp(null)
|
||||
.groupId(19)
|
||||
.locPitch(123.004f)
|
||||
.build();
|
||||
@ -57,6 +62,7 @@ public class PlayerAuthTest {
|
||||
// then
|
||||
assertThat(auth.getEmail(), nullValue());
|
||||
assertThat(auth.getLastLogin(), nullValue());
|
||||
assertThat(auth.getLastIp(), nullValue());
|
||||
assertThat(auth.getGroupId(), equalTo(19));
|
||||
assertThat(auth.getPitch(), equalTo(123.004f));
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ public class FlatFileIntegrationTest {
|
||||
assertThat(getName("bobby", authList), hasAuthBasicData("bobby", "bobby", null, "123.45.67.89"));
|
||||
assertThat(getName("bobby", authList), hasAuthLocation(1.05, 2.1, 4.2, "world", 0, 0));
|
||||
assertThat(getName("bobby", authList).getPassword(), equalToHash("$SHA$11aa0706173d7272$dbba966"));
|
||||
assertThat(getName("twofields", authList), hasAuthBasicData("twofields", "twofields", null, "127.0.0.1"));
|
||||
assertThat(getName("twofields", authList), hasAuthBasicData("twofields", "twofields", null, null));
|
||||
assertThat(getName("twofields", authList).getPassword(), equalToHash("hash1234"));
|
||||
assertThat(getName("threefields", authList), hasAuthBasicData("threefields", "threefields", null, "33.33.33.33"));
|
||||
assertThat(getName("fourfields", authList), hasAuthBasicData("fourfields", "fourfields", null, "4.4.4.4"));
|
||||
|
||||
@ -76,7 +76,7 @@ public class MySqlIntegrationTest extends AbstractDataSourceIntegrationTest {
|
||||
@Override
|
||||
protected DataSource getDataSource(String saltColumn) {
|
||||
when(settings.getProperty(DatabaseSettings.MYSQL_COL_SALT)).thenReturn(saltColumn);
|
||||
return MySqlTestUtil.createMySql(settings, hikariSource);
|
||||
return SqlDataSourceTestUtil.createMySql(settings, hikariSource);
|
||||
}
|
||||
|
||||
private static <T> void set(Property<T> property, T value) {
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
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.settings.Settings;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Test util for the MySQL data source.
|
||||
*/
|
||||
public final class MySqlTestUtil {
|
||||
|
||||
private MySqlTestUtil() {
|
||||
}
|
||||
|
||||
public static MySQL createMySql(Settings settings, HikariDataSource hikariDataSource) {
|
||||
MySqlExtensionsFactory extensionsFactory = mock(MySqlExtensionsFactory.class);
|
||||
given(extensionsFactory.buildExtension(any())).willReturn(mock(MySqlExtension.class));
|
||||
return createMySql(settings, hikariDataSource, extensionsFactory);
|
||||
}
|
||||
|
||||
public static MySQL createMySql(Settings settings, HikariDataSource hikariDataSource,
|
||||
MySqlExtensionsFactory extensionsFactory) {
|
||||
return new MySQL(settings, hikariDataSource, extensionsFactory);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
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.settings.Settings;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Test util for the SQL data sources.
|
||||
*/
|
||||
public final class SqlDataSourceTestUtil {
|
||||
|
||||
private SqlDataSourceTestUtil() {
|
||||
}
|
||||
|
||||
public static MySQL createMySql(Settings settings, HikariDataSource hikariDataSource) {
|
||||
MySqlExtensionsFactory extensionsFactory = mock(MySqlExtensionsFactory.class);
|
||||
given(extensionsFactory.buildExtension(any())).willReturn(mock(MySqlExtension.class));
|
||||
return new MySQL(settings, hikariDataSource, extensionsFactory);
|
||||
}
|
||||
|
||||
public static SQLite createSqlite(Settings settings, Connection connection) {
|
||||
return new SQLite(settings, connection) {
|
||||
// Override reload() so it doesn't run SQLite#connect, since we're given a specific Connection to use
|
||||
@Override
|
||||
public void reload() {
|
||||
try {
|
||||
this.setup();
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SQLite createSqliteAndInitialize(Settings settings, Connection connection) {
|
||||
SQLite sqLite = createSqlite(settings, connection);
|
||||
try {
|
||||
sqLite.setup();
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return sqLite;
|
||||
}
|
||||
}
|
||||
@ -4,10 +4,17 @@ import fr.xephi.authme.TestHelper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
|
||||
|
||||
@ -40,4 +47,108 @@ public class SqlDataSourceUtilsTest {
|
||||
// then
|
||||
verify(logger).warning(argThat(containsString(msg)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFetchNullableStatus() throws SQLException {
|
||||
// given
|
||||
String tableName = "data";
|
||||
String columnName = "category";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.getInt("NULLABLE")).willReturn(DatabaseMetaData.columnNullable);
|
||||
given(resultSet.next()).willReturn(true);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
boolean result = SqlDataSourceUtils.isNotNullColumn(metaData, tableName, columnName);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFalseForUnknownNullableStatus() throws SQLException {
|
||||
// given
|
||||
String tableName = "comments";
|
||||
String columnName = "author";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.getInt("NULLABLE")).willReturn(DatabaseMetaData.columnNullableUnknown);
|
||||
given(resultSet.next()).willReturn(true);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
boolean result = SqlDataSourceUtils.isNotNullColumn(metaData, tableName, columnName);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowForUnknownColumnInNullableCheck() throws SQLException {
|
||||
// given
|
||||
String tableName = "data";
|
||||
String columnName = "unknown";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.next()).willReturn(false);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
SqlDataSourceUtils.isNotNullColumn(metaData, tableName, columnName);
|
||||
|
||||
// then - expect exception
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetDefaultValue() throws SQLException {
|
||||
// given
|
||||
String tableName = "data";
|
||||
String columnName = "category";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.getObject("COLUMN_DEF")).willReturn("Literature");
|
||||
given(resultSet.next()).willReturn(true);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
Object defaultValue = SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, columnName);
|
||||
|
||||
// then
|
||||
assertThat(defaultValue, equalTo("Literature"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void shouldThrowForUnknownColumnInDefaultValueRetrieval() throws SQLException {
|
||||
// given
|
||||
String tableName = "data";
|
||||
String columnName = "unknown";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.next()).willReturn(false);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, columnName);
|
||||
|
||||
// then - expect exception
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleNullDefaultValue() throws SQLException {
|
||||
// given
|
||||
String tableName = "data";
|
||||
String columnName = "category";
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.getObject("COLUMN_DEF")).willReturn(null);
|
||||
given(resultSet.next()).willReturn(true);
|
||||
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
|
||||
given(metaData.getColumns(null, null, tableName, columnName)).willReturn(resultSet);
|
||||
|
||||
// when
|
||||
Object defaultValue = SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, columnName);
|
||||
|
||||
// then
|
||||
assertThat(defaultValue, nullValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import static fr.xephi.authme.AuthMeMatchers.hasAuthLocation;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@ -75,7 +76,7 @@ public class LoginSecurityConverterTest {
|
||||
assertThat(captor.getAllValues().get(0).getLastLogin(), equalTo(1494242093652L));
|
||||
assertThat(captor.getAllValues().get(0).getRegistrationDate(), equalTo(1494242093400L));
|
||||
assertThat(captor.getAllValues().get(0).getPassword(), equalToHash("$2a$10$E1Ri7XKeIIBv4qVaiPplgepT7QH9xGFh3hbHfcmCjq7hiW.UBTiGK"));
|
||||
assertThat(captor.getAllValues().get(0).getLastIp(), equalTo("127.0.0.1"));
|
||||
assertThat(captor.getAllValues().get(0).getLastIp(), nullValue());
|
||||
|
||||
assertThat(captor.getAllValues().get(1).getNickname(), equalTo("player2"));
|
||||
assertThat(captor.getAllValues().get(1).getLastLogin(), equalTo(1494242174589L));
|
||||
@ -84,7 +85,7 @@ public class LoginSecurityConverterTest {
|
||||
assertThat(captor.getAllValues().get(2).getRealName(), equalTo("Player3"));
|
||||
assertThat(captor.getAllValues().get(2).getPassword(), equalToHash("$2a$10$WFui8KSXMLDOVXKFpCLyPukPi4M82w1cv/rNojsAnwJjba3pp8sba"));
|
||||
assertThat(captor.getAllValues().get(2), hasAuthLocation(14.24, 67.99, -12.83, "hubb", -10f, 185f));
|
||||
assertThat(captor.getAllValues().get(2).getLastIp(), equalTo("127.0.0.1"));
|
||||
assertThat(captor.getAllValues().get(2).getLastIp(), nullValue());
|
||||
assertIsCloseTo(captor.getAllValues().get(2).getRegistrationDate(), System.currentTimeMillis(), 500L);
|
||||
}
|
||||
|
||||
@ -108,7 +109,7 @@ public class LoginSecurityConverterTest {
|
||||
assertThat(captor.getAllValues().get(0).getRealName(), equalTo("Player1"));
|
||||
assertThat(captor.getAllValues().get(0).getLastLogin(), equalTo(1494242093000L));
|
||||
assertThat(captor.getAllValues().get(0).getPassword(), equalToHash("$2a$10$E1Ri7XKeIIBv4qVaiPplgepT7QH9xGFh3hbHfcmCjq7hiW.UBTiGK"));
|
||||
assertThat(captor.getAllValues().get(0).getLastIp(), equalTo("127.0.0.1"));
|
||||
assertThat(captor.getAllValues().get(0).getLastIp(), nullValue());
|
||||
assertIsCloseTo(captor.getAllValues().get(0).getRegistrationDate(), 1494201600000L, 12 * 60 * 60 * 1000);
|
||||
|
||||
assertThat(captor.getAllValues().get(1).getNickname(), equalTo("player2"));
|
||||
|
||||
@ -121,7 +121,7 @@ public class EmailRegisterExecutorProviderTest {
|
||||
PlayerAuth auth = executor.buildPlayerAuth(params);
|
||||
|
||||
// then
|
||||
assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", "127.0.0.1"));
|
||||
assertThat(auth, hasAuthBasicData("veronica", "Veronica", "test@example.com", null));
|
||||
assertThat(auth.getRegistrationIp(), equalTo("123.45.67.89"));
|
||||
assertIsCloseTo(auth.getRegistrationDate(), System.currentTimeMillis(), 1000);
|
||||
assertThat(auth.getPassword().getHash(), stringWithLength(12));
|
||||
|
||||
@ -103,7 +103,7 @@ public class PasswordRegisterExecutorTest {
|
||||
PlayerAuth auth = executor.buildPlayerAuth(params);
|
||||
|
||||
// then
|
||||
assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", "127.0.0.1"));
|
||||
assertThat(auth, hasAuthBasicData("s1m0n", "S1m0N", "mail@example.org", null));
|
||||
assertThat(auth.getRegistrationIp(), equalTo("123.45.67.89"));
|
||||
assertIsCloseTo(auth.getRegistrationDate(), System.currentTimeMillis(), 500);
|
||||
assertThat(auth.getPassword(), equalToHash("pass"));
|
||||
|
||||
@ -33,7 +33,7 @@ public class PlayerAuthBuilderHelperTest {
|
||||
PlayerAuth auth = PlayerAuthBuilderHelper.createPlayerAuth(player, hashedPassword, email);
|
||||
|
||||
// then
|
||||
assertThat(auth, hasAuthBasicData("noah", "Noah", email, "127.0.0.1"));
|
||||
assertThat(auth, hasAuthBasicData("noah", "Noah", email, null));
|
||||
assertThat(auth.getRegistrationIp(), equalTo("192.168.34.47"));
|
||||
assertThat(Math.abs(auth.getRegistrationDate() - System.currentTimeMillis()), lessThan(1000L));
|
||||
assertThat(auth.getPassword(), equalToHash("myHash0001"));
|
||||
|
||||
@ -213,6 +213,30 @@ public class SessionServiceTest {
|
||||
verify(dataSource).getAuth(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandlePlayerAuthWithNullLastIp() {
|
||||
// given
|
||||
String name = "Charles";
|
||||
Player player = mockPlayerWithNameAndIp(name, "144.117.118.145");
|
||||
given(dataSource.hasSession(name)).willReturn(true);
|
||||
PlayerAuth auth = PlayerAuth.builder()
|
||||
.name(name)
|
||||
.lastIp(null)
|
||||
.lastLogin(System.currentTimeMillis()).build();
|
||||
given(dataSource.getAuth(name)).willReturn(auth);
|
||||
|
||||
// when
|
||||
boolean result = sessionService.canResumeSession(player);
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(false));
|
||||
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
|
||||
verify(dataSource).hasSession(name);
|
||||
verify(dataSource).setUnlogged(name);
|
||||
verify(dataSource).revokeSession(name);
|
||||
verify(dataSource).getAuth(name);
|
||||
}
|
||||
|
||||
private static Player mockPlayerWithNameAndIp(String name, String ip) {
|
||||
Player player = mock(Player.class);
|
||||
given(player.getName()).willReturn(name);
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user