diff --git a/checkstyle.xml b/checkstyle.xml index 9bae4d4c..cca3b330 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -220,6 +220,14 @@ + + + + + + + + diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateManagement.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateManagement.java index f0c27651..9a59f550 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateManagement.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateManagement.java @@ -80,6 +80,40 @@ public abstract class FloodgateManagement

authPlugin = core.getAuthPluginHook(); try { @@ -119,13 +153,17 @@ public abstract class FloodgateManagement

+ *

  • False
  • + *
  • True
  • + *
  • Linked
  • + *
  • Not Migrated
  • + * + * @param num the number, most likely loaded from the database + * @return FloodgateStatus on success, null otherwise + */ + public static FloodgateState fromInt(int num) { + // using Enum.values()[i] is expensive as per https://stackoverflow.com/a/8762387/9767089 + switch (num) { + case 0: + return FloodgateState.FALSE; + case 1: + return FloodgateState.TRUE; + case 2: + return FloodgateState.LINKED; + case 3: + return FloodgateState.NOT_MIGRATED; + default: + return null; + } + } +} diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/JoinManagement.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/JoinManagement.java index 75b9f500..c5c41aac 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/JoinManagement.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/JoinManagement.java @@ -49,17 +49,32 @@ public abstract class JoinManagement

    { public void onLogin(String username, S source) { core.getPlugin().getLog().info("Handling player {}", username); + + //check if the player is connecting through Bedrock Edition + if (bedrockService != null && bedrockService.isBedrockConnection(username)) { + //perform Bedrock specific checks and skip Java checks if no longer needed + if (bedrockService.performChecks(username, source)) { + return; + } + } + StoredProfile profile = core.getStorage().loadProfile(username); + + //can't be a premium Java player, if it's not saved in the database if (profile == null) { return; } - //check if the player is connecting through Bedrock Edition - if (bedrockService != null && bedrockService.isBedrockConnection(username)) { - //perform Bedrock specific checks and skip Java checks, if they are not needed - if (bedrockService.performChecks(username, source)) { + if (profile.isFloodgateMigrated()) { + if (profile.getFloodgate() == FloodgateState.TRUE) { + // migrated and enabled floodgate player, however the above bedrocks fails, so the current connection + // isn't premium return; } + } else { + profile.setFloodgate(FloodgateState.FALSE); + core.getPlugin().getLog().info( + "Player {} will be migrated to the v2 database schema as a JAVA user", username); } callFastLoginPreLoginEvent(username, source, profile); @@ -139,6 +154,12 @@ public abstract class JoinManagement

    { if (core.getConfig().get("nameChangeCheck", false)) { StoredProfile storedProfile = core.getStorage().loadProfile(profile.getId()); if (storedProfile != null) { + if (storedProfile.getFloodgate() == FloodgateState.TRUE) { + core.getPlugin().getLog() + .info("Player {} is already stored by FastLogin as a Bedrock Edition player.", username); + return false; + } + //uuid exists in the database core.getPlugin().getLog().info("GameProfile {} changed it's username", profile); diff --git a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java index 178a2fe8..ade10a40 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java @@ -26,11 +26,13 @@ package com.github.games647.fastlogin.core.storage; import com.github.games647.craftapi.UUIDAdapter; +import com.github.games647.fastlogin.core.shared.FloodgateState; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.slf4j.Logger; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -56,13 +58,19 @@ public abstract class SQLStorage implements AuthStorage { + "UNIQUE (`Name`) " + ')'; - protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1"; - protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1"; + protected static final String ADD_FLOODGATE_COLUMN_STMT = "ALTER TABLE `" + PREMIUM_TABLE + + "` ADD COLUMN `Floodgate` INTEGER(3)"; + + protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + + "` WHERE `Name`=? LIMIT 1"; + protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + + "` WHERE `UUID`=? LIMIT 1"; protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE - + "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) "; + + "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) "; // limit not necessary here, because it's unique protected static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE - + "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?"; + + "` SET `UUID`=?, `Name`=?, `Premium`=?, `Floodgate`=?, `LastIp`=?, " + + "`LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?"; protected final Logger log; protected final HikariDataSource dataSource; @@ -81,11 +89,23 @@ public abstract class SQLStorage implements AuthStorage { // choose surrogate PK(ID), because UUID can be null for offline players // if UUID is always Premium UUID we would have to update offline player entries on insert // name cannot be PK, because it can be changed for premium players - //todo: add unique uuid index usage try (Connection con = dataSource.getConnection(); - Statement createStmt = con.createStatement()) { - createStmt.executeUpdate(CREATE_TABLE_STMT); + Statement stmt = con.createStatement()) { + stmt.executeUpdate(getCreateTableStmt()); + + // add Floodgate column + DatabaseMetaData md = con.getMetaData(); + if (isColumnMissing(md, "Floodgate")) { + stmt.executeUpdate(ADD_FLOODGATE_COLUMN_STMT); + } + + } + } + + private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException { + try (ResultSet rs = metaData.getColumns(null, null, PREMIUM_TABLE, columnName)) { + return !rs.next(); } } @@ -97,7 +117,8 @@ public abstract class SQLStorage implements AuthStorage { loadStmt.setString(1, name); try (ResultSet resultSet = loadStmt.executeQuery()) { - return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, "")); + return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, + FloodgateState.FALSE, "")); } } catch (SQLException sqlEx) { log.error("Failed to query profile: {}", name, sqlEx); @@ -124,15 +145,25 @@ public abstract class SQLStorage implements AuthStorage { private Optional parseResult(ResultSet resultSet) throws SQLException { if (resultSet.next()) { - long userId = resultSet.getInt(1); + long userId = resultSet.getInt("UserID"); - UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null); + UUID uuid = Optional.ofNullable(resultSet.getString("UUID")).map(UUIDAdapter::parseId).orElse(null); - String name = resultSet.getString(3); - boolean premium = resultSet.getBoolean(4); - String lastIp = resultSet.getString(5); - Instant lastLogin = resultSet.getTimestamp(6).toInstant(); - return Optional.of(new StoredProfile(userId, uuid, name, premium, lastIp, lastLogin)); + String name = resultSet.getString("Name"); + boolean premium = resultSet.getBoolean("Premium"); + int floodgateNum = resultSet.getInt("Floodgate"); + FloodgateState floodgate; + + // if the player wasn't migrated to the new database format + if (resultSet.wasNull()) { + floodgate = FloodgateState.NOT_MIGRATED; + } else { + floodgate = FloodgateState.fromInt(floodgateNum); + } + + String lastIp = resultSet.getString("LastIp"); + Instant lastLogin = resultSet.getTimestamp("LastLogin").toInstant(); + return Optional.of(new StoredProfile(userId, uuid, name, premium, floodgate, lastIp, lastLogin)); } return Optional.empty(); @@ -150,9 +181,10 @@ public abstract class SQLStorage implements AuthStorage { saveStmt.setString(1, uuid); saveStmt.setString(2, playerProfile.getName()); saveStmt.setBoolean(3, playerProfile.isPremium()); - saveStmt.setString(4, playerProfile.getLastIp()); + saveStmt.setInt(4, playerProfile.getFloodgate().getValue()); + saveStmt.setString(5, playerProfile.getLastIp()); - saveStmt.setLong(5, playerProfile.getRowId()); + saveStmt.setLong(6, playerProfile.getRowId()); saveStmt.execute(); } } else { @@ -161,7 +193,9 @@ public abstract class SQLStorage implements AuthStorage { saveStmt.setString(2, playerProfile.getName()); saveStmt.setBoolean(3, playerProfile.isPremium()); - saveStmt.setString(4, playerProfile.getLastIp()); + saveStmt.setBoolean(3, playerProfile.isPremium()); + saveStmt.setInt(4, playerProfile.getFloodgate().getValue()); + saveStmt.setString(5, playerProfile.getLastIp()); saveStmt.execute(); try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) { @@ -179,6 +213,14 @@ public abstract class SQLStorage implements AuthStorage { } } + /** + * SQLite has a slightly different syntax, so this will be overridden by SQLiteStorage + * @return An SQL Statement to create the `premium` table + */ + protected String getCreateTableStmt() { + return CREATE_TABLE_STMT; + } + @Override public void close() { dataSource.close(); diff --git a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLiteStorage.java b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLiteStorage.java index b428cbc1..195cf2bb 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLiteStorage.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLiteStorage.java @@ -31,15 +31,23 @@ import org.sqlite.JDBC; import org.sqlite.SQLiteConfig; import java.nio.file.Path; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SQLiteStorage extends SQLStorage { + protected static final String CREATE_TABLE_STMT = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` (" + + "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, " + + "`UUID` CHAR(36), " + + "`Name` VARCHAR(16) NOT NULL, " + + "`Premium` BOOLEAN NOT NULL, " + + "`LastIp` VARCHAR(255) NOT NULL, " + + "`LastLogin` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + //the premium shouldn't steal the cracked account by changing the name + + "UNIQUE (`Name`) " + + ')'; + private static final String SQLITE_DRIVER = "org.sqlite.SQLiteDataSource"; private final Lock lock = new ReentrantLock(); @@ -103,12 +111,9 @@ public class SQLiteStorage extends SQLStorage { } @Override - public void createTables() throws SQLException { - try (Connection con = dataSource.getConnection(); - Statement createStmt = con.createStatement()) { - // SQLite has a different syntax for auto increment - createStmt.executeUpdate(CREATE_TABLE_STMT.replace("AUTO_INCREMENT", "AUTOINCREMENT")); - } + protected String getCreateTableStmt() { + // SQLite has a different syntax for auto increment + return CREATE_TABLE_STMT.replace("AUTO_INCREMENT", "AUTOINCREMENT"); } private static String replacePathVariables(Path dataFolder, String input) { diff --git a/core/src/main/java/com/github/games647/fastlogin/core/storage/StoredProfile.java b/core/src/main/java/com/github/games647/fastlogin/core/storage/StoredProfile.java index 0da7775c..625d383f 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/storage/StoredProfile.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/storage/StoredProfile.java @@ -26,6 +26,7 @@ package com.github.games647.fastlogin.core.storage; import com.github.games647.craftapi.model.Profile; +import com.github.games647.fastlogin.core.shared.FloodgateState; import java.time.Instant; import java.util.Objects; @@ -39,20 +40,28 @@ public class StoredProfile extends Profile { private final ReentrantLock saveLock = new ReentrantLock(); private boolean premium; + private FloodgateState floodgate; private String lastIp; private Instant lastLogin; - public StoredProfile(long rowId, UUID uuid, String playerName, boolean premium, String lastIp, Instant lastLogin) { + public StoredProfile(long rowId, UUID uuid, String playerName, boolean premium, FloodgateState floodgate, + String lastIp, Instant lastLogin) { super(uuid, playerName); this.rowId = rowId; this.premium = premium; + this.floodgate = floodgate; this.lastIp = lastIp; this.lastLogin = lastLogin; } + public StoredProfile(UUID uuid, String playerName, boolean premium, FloodgateState isFloodgate, String lastIp) { + this(-1, uuid, playerName, premium, isFloodgate, lastIp, Instant.now()); + } + + @Deprecated public StoredProfile(UUID uuid, String playerName, boolean premium, String lastIp) { - this(-1, uuid, playerName, premium, lastIp, Instant.now()); + this(-1, uuid, playerName, premium, FloodgateState.FALSE, lastIp, Instant.now()); } public ReentrantLock getSaveLock() { @@ -96,6 +105,18 @@ public class StoredProfile extends Profile { this.premium = premium; } + public synchronized FloodgateState getFloodgate() { + return floodgate; + } + + public synchronized boolean isFloodgateMigrated() { + return floodgate != FloodgateState.NOT_MIGRATED; + } + + public synchronized void setFloodgate(FloodgateState floodgate) { + this.floodgate = floodgate; + } + public synchronized String getLastIp() { return lastIp; } @@ -128,7 +149,7 @@ public class StoredProfile extends Profile { } return rowId == that.rowId && premium == that.premium - && Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin); + && Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin); } @Override @@ -141,6 +162,7 @@ public class StoredProfile extends Profile { return this.getClass().getSimpleName() + '{' + "rowId=" + rowId + ", premium=" + premium + + ", floodgate=" + floodgate + ", lastIp='" + lastIp + '\'' + ", lastLogin=" + lastLogin + "} " + super.toString();