From db5b818a80e5b7095ffbc68da5be1e491e352d2f Mon Sep 17 00:00:00 2001 From: Smart123s <28480228+Smart123s@users.noreply.github.com> Date: Sat, 22 Jan 2022 19:20:36 +0100 Subject: [PATCH] Add `Floodgate` variable to StoredProfile/Database --- .../fastlogin/core/shared/FloodgateState.java | 70 +++++++++++++++++++ .../fastlogin/core/storage/SQLStorage.java | 57 ++++++++++++--- .../fastlogin/core/storage/SQLiteStorage.java | 23 +++--- .../fastlogin/core/storage/StoredProfile.java | 28 +++++++- 4 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java new file mode 100644 index 00000000..874deb9b --- /dev/null +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateState.java @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.core.shared; + +public enum FloodgateState { + FALSE(0), + TRUE(1), + LINKED(2), + NOT_MIGRATED(3); + + private int value; + + FloodgateState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * Convert a number to FloodgateState + *
    + *
  1. False
  2. + *
  3. True
  4. + *
  5. Linked
  6. + *
  7. Not Migrated
  8. + *
+ * @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/storage/SQLStorage.java b/core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java index e1891588..f10a0796 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,14 @@ package com.github.games647.fastlogin.core.storage; import com.github.games647.craftapi.UUIDAdapter; +import com.github.games647.fastlogin.core.StoredProfile; +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; @@ -50,13 +53,15 @@ public abstract class SQLStorage implements AuthStorage { + "`UUID` CHAR(36), " + "`Name` VARCHAR(16) NOT NULL, " + "`Premium` BOOLEAN NOT NULL, " - + "`Floodgate` 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`) " + ')'; + 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 @@ -85,11 +90,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(); } } @@ -101,7 +118,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, false, "")); + return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, + FloodgateState.FALSE, "")); } } catch (SQLException sqlEx) { log.error("Failed to query profile: {}", name, sqlEx); @@ -134,9 +152,19 @@ public abstract class SQLStorage implements AuthStorage { 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, lastIp, lastLogin)); + return Optional.of(new StoredProfile(userId, uuid, name, premium, floodgate, lastIp, lastLogin)); } return Optional.empty(); @@ -154,9 +182,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 { @@ -165,7 +194,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()) { @@ -183,6 +214,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();