Skip to content

Commit

Permalink
Merge pull request #709 from Smart123s/feature/floodgate/newdb
Browse files Browse the repository at this point in the history
Differentiate Floodgate players in database
  • Loading branch information
games647 committed Jan 15, 2024
2 parents cd67797 + 434a276 commit 0021bc4
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 37 deletions.
8 changes: 8 additions & 0 deletions checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@
<property name="optional" value="true"/>
</module>

<!-- Suppress filters via comments -->
<!-- https://stackoverflow.com/a/4023351/9767089 -->
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)"/>
<property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)"/>
<property name="checkFormat" value="$1"/>
</module>

</module>

</module>
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,40 @@ public void run() {
}

profile = core.getStorage().loadProfile(username);

if (profile.isSaved()) {
if (profile.isFloodgateMigrated()) {
if (profile.getFloodgate() == FloodgateState.TRUE && isLinked) {
core.getPlugin().getLog()
.info("Player {} is already stored by FastLogin as a non-linked Bedrock Edition player",
username);
return;
} else if (profile.getFloodgate() == FloodgateState.FALSE && isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
core.getPlugin().getLog().info(
"Player {} will be changed from a Java player to a linked Floodgate player",
username);
}
} else {
if (isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
core.getPlugin().getLog().info(
"Player {} will be migrated to the v2 database schema as a linked Floodgate user",
username);
} else {
profile.setFloodgate(FloodgateState.TRUE);
core.getPlugin().getLog().info(
"Player {} will be migrated to the v2 database schema as a Floodgate user", username);
}
}
} else {
if (isLinked) {
profile.setFloodgate(FloodgateState.LINKED);
} else {
profile.setFloodgate(FloodgateState.TRUE);
}
}

AuthPlugin<P> authPlugin = core.getAuthPluginHook();

try {
Expand Down Expand Up @@ -119,13 +153,17 @@ public void run() {
}
}

// defer auto registration, if it's not enabled in the config
if (!isRegistered && !isAutoAuthAllowed(autoRegisterFloodgate)) {
return;
}

//logging in from bedrock for a second time threw an error with UUID
if (profile == null) {
profile = new StoredProfile(getUUID(player), username, true, getAddress(player).toString());
// stop the auto login procedure, if the current connection's parameters don't match the one stored in our
// database
// ex. we stored a LINKED account, but the current connection is not linked
if ((profile.getFloodgate() == FloodgateState.LINKED && !isLinked)
|| (profile.getFloodgate() == FloodgateState.TRUE && isLinked)) {
return;
}

//start Bukkit/Bungee specific tasks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 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 {

/**
* Purely Java profile
*/
FALSE(0),

/**
* Purely Bedrock profile
*/
TRUE(1),

/**
* Bedrock profile is bidirectional associated with the Java Mojang profile.
*/
LINKED(2),

/**
* Data before floodgate database migration. Floodgate state is unknown.
*/
NOT_MIGRATED(3);

private int value;

FloodgateState(int value) {
this.value = value;
}

public int getValue() {
return value;
}

/**
* Convert a number to FloodgateState
* <ol start="0">
* <li>False</li>
* <li>True</li>
* <li>Linked</li>
* <li>Not Migrated</li>
* </ol>
* @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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,32 @@ public JoinManagement(FastLoginCore<P, C, ?> core, AuthPlugin<P> authHook, Bedro

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);
Expand Down Expand Up @@ -139,6 +154,12 @@ private boolean checkNameChange(S source, String username, Profile profile) {
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -81,11 +89,23 @@ public void createTables() throws SQLException {
// 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();
}
}

Expand All @@ -97,7 +117,8 @@ public StoredProfile loadProfile(String name) {
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);
Expand All @@ -124,15 +145,25 @@ public StoredProfile loadProfile(UUID uuid) {

private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
long userId = resultSet.getInt(1);
long userId = resultSet.getInt("UserID");

UUID uuid = Optional.ofNullable(resultSet.getString("UUID")).map(UUIDAdapter::parseId).orElse(null);

UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
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 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 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();
Expand All @@ -150,9 +181,10 @@ public void save(StoredProfile playerProfile) {
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 {
Expand All @@ -161,7 +193,9 @@ public void save(StoredProfile playerProfile) {

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()) {
Expand All @@ -179,6 +213,14 @@ public void save(StoredProfile playerProfile) {
}
}

/**
* 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();
Expand Down
Loading

0 comments on commit 0021bc4

Please sign in to comment.