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();