Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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