Skip to content

Commit 0021bc4

Browse files
author
games647
authored
Merge pull request #709 from Smart123s/feature/floodgate/newdb
Differentiate Floodgate players in database
2 parents cd67797 + 434a276 commit 0021bc4

File tree

7 files changed

+259
-37
lines changed

7 files changed

+259
-37
lines changed

checkstyle.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,14 @@
220220
<property name="optional" value="true"/>
221221
</module>
222222

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

225233
</module>

core/src/main/java/com/github/games647/fastlogin/core/shared/FloodgateManagement.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,40 @@ public void run() {
8080
}
8181

8282
profile = core.getStorage().loadProfile(username);
83+
84+
if (profile.isSaved()) {
85+
if (profile.isFloodgateMigrated()) {
86+
if (profile.getFloodgate() == FloodgateState.TRUE && isLinked) {
87+
core.getPlugin().getLog()
88+
.info("Player {} is already stored by FastLogin as a non-linked Bedrock Edition player",
89+
username);
90+
return;
91+
} else if (profile.getFloodgate() == FloodgateState.FALSE && isLinked) {
92+
profile.setFloodgate(FloodgateState.LINKED);
93+
core.getPlugin().getLog().info(
94+
"Player {} will be changed from a Java player to a linked Floodgate player",
95+
username);
96+
}
97+
} else {
98+
if (isLinked) {
99+
profile.setFloodgate(FloodgateState.LINKED);
100+
core.getPlugin().getLog().info(
101+
"Player {} will be migrated to the v2 database schema as a linked Floodgate user",
102+
username);
103+
} else {
104+
profile.setFloodgate(FloodgateState.TRUE);
105+
core.getPlugin().getLog().info(
106+
"Player {} will be migrated to the v2 database schema as a Floodgate user", username);
107+
}
108+
}
109+
} else {
110+
if (isLinked) {
111+
profile.setFloodgate(FloodgateState.LINKED);
112+
} else {
113+
profile.setFloodgate(FloodgateState.TRUE);
114+
}
115+
}
116+
83117
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
84118

85119
try {
@@ -119,13 +153,17 @@ public void run() {
119153
}
120154
}
121155

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

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

131169
//start Bukkit/Bungee specific tasks
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* SPDX-License-Identifier: MIT
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2015-2023 games647 and contributors
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
package com.github.games647.fastlogin.core.shared;
27+
28+
public enum FloodgateState {
29+
30+
/**
31+
* Purely Java profile
32+
*/
33+
FALSE(0),
34+
35+
/**
36+
* Purely Bedrock profile
37+
*/
38+
TRUE(1),
39+
40+
/**
41+
* Bedrock profile is bidirectional associated with the Java Mojang profile.
42+
*/
43+
LINKED(2),
44+
45+
/**
46+
* Data before floodgate database migration. Floodgate state is unknown.
47+
*/
48+
NOT_MIGRATED(3);
49+
50+
private int value;
51+
52+
FloodgateState(int value) {
53+
this.value = value;
54+
}
55+
56+
public int getValue() {
57+
return value;
58+
}
59+
60+
/**
61+
* Convert a number to FloodgateState
62+
* <ol start="0">
63+
* <li>False</li>
64+
* <li>True</li>
65+
* <li>Linked</li>
66+
* <li>Not Migrated</li>
67+
* </ol>
68+
* @param num the number, most likely loaded from the database
69+
* @return FloodgateStatus on success, null otherwise
70+
*/
71+
public static FloodgateState fromInt(int num) {
72+
// using Enum.values()[i] is expensive as per https://stackoverflow.com/a/8762387/9767089
73+
switch (num) {
74+
case 0:
75+
return FloodgateState.FALSE;
76+
case 1:
77+
return FloodgateState.TRUE;
78+
case 2:
79+
return FloodgateState.LINKED;
80+
case 3:
81+
return FloodgateState.NOT_MIGRATED;
82+
default:
83+
return null;
84+
}
85+
}
86+
}

core/src/main/java/com/github/games647/fastlogin/core/shared/JoinManagement.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,32 @@ public JoinManagement(FastLoginCore<P, C, ?> core, AuthPlugin<P> authHook, Bedro
4949

5050
public void onLogin(String username, S source) {
5151
core.getPlugin().getLog().info("Handling player {}", username);
52+
53+
//check if the player is connecting through Bedrock Edition
54+
if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
55+
//perform Bedrock specific checks and skip Java checks if no longer needed
56+
if (bedrockService.performChecks(username, source)) {
57+
return;
58+
}
59+
}
60+
5261
StoredProfile profile = core.getStorage().loadProfile(username);
62+
63+
//can't be a premium Java player, if it's not saved in the database
5364
if (profile == null) {
5465
return;
5566
}
5667

57-
//check if the player is connecting through Bedrock Edition
58-
if (bedrockService != null && bedrockService.isBedrockConnection(username)) {
59-
//perform Bedrock specific checks and skip Java checks, if they are not needed
60-
if (bedrockService.performChecks(username, source)) {
68+
if (profile.isFloodgateMigrated()) {
69+
if (profile.getFloodgate() == FloodgateState.TRUE) {
70+
// migrated and enabled floodgate player, however the above bedrocks fails, so the current connection
71+
// isn't premium
6172
return;
6273
}
74+
} else {
75+
profile.setFloodgate(FloodgateState.FALSE);
76+
core.getPlugin().getLog().info(
77+
"Player {} will be migrated to the v2 database schema as a JAVA user", username);
6378
}
6479

6580
callFastLoginPreLoginEvent(username, source, profile);
@@ -139,6 +154,12 @@ private boolean checkNameChange(S source, String username, Profile profile) {
139154
if (core.getConfig().get("nameChangeCheck", false)) {
140155
StoredProfile storedProfile = core.getStorage().loadProfile(profile.getId());
141156
if (storedProfile != null) {
157+
if (storedProfile.getFloodgate() == FloodgateState.TRUE) {
158+
core.getPlugin().getLog()
159+
.info("Player {} is already stored by FastLogin as a Bedrock Edition player.", username);
160+
return false;
161+
}
162+
142163
//uuid exists in the database
143164
core.getPlugin().getLog().info("GameProfile {} changed it's username", profile);
144165

core/src/main/java/com/github/games647/fastlogin/core/storage/SQLStorage.java

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
package com.github.games647.fastlogin.core.storage;
2727

2828
import com.github.games647.craftapi.UUIDAdapter;
29+
import com.github.games647.fastlogin.core.shared.FloodgateState;
2930
import com.zaxxer.hikari.HikariConfig;
3031
import com.zaxxer.hikari.HikariDataSource;
3132
import org.slf4j.Logger;
3233

3334
import java.sql.Connection;
35+
import java.sql.DatabaseMetaData;
3436
import java.sql.PreparedStatement;
3537
import java.sql.ResultSet;
3638
import java.sql.SQLException;
@@ -56,13 +58,19 @@ public abstract class SQLStorage implements AuthStorage {
5658
+ "UNIQUE (`Name`) "
5759
+ ')';
5860

59-
protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
60-
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
61+
protected static final String ADD_FLOODGATE_COLUMN_STMT = "ALTER TABLE `" + PREMIUM_TABLE
62+
+ "` ADD COLUMN `Floodgate` INTEGER(3)";
63+
64+
protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE
65+
+ "` WHERE `Name`=? LIMIT 1";
66+
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE
67+
+ "` WHERE `UUID`=? LIMIT 1";
6168
protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
62-
+ "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
69+
+ "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) ";
6370
// limit not necessary here, because it's unique
6471
protected static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
65-
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
72+
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `Floodgate`=?, `LastIp`=?, "
73+
+ "`LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
6674

6775
protected final Logger log;
6876
protected final HikariDataSource dataSource;
@@ -81,11 +89,23 @@ public void createTables() throws SQLException {
8189
// choose surrogate PK(ID), because UUID can be null for offline players
8290
// if UUID is always Premium UUID we would have to update offline player entries on insert
8391
// name cannot be PK, because it can be changed for premium players
84-
8592
//todo: add unique uuid index usage
8693
try (Connection con = dataSource.getConnection();
87-
Statement createStmt = con.createStatement()) {
88-
createStmt.executeUpdate(CREATE_TABLE_STMT);
94+
Statement stmt = con.createStatement()) {
95+
stmt.executeUpdate(getCreateTableStmt());
96+
97+
// add Floodgate column
98+
DatabaseMetaData md = con.getMetaData();
99+
if (isColumnMissing(md, "Floodgate")) {
100+
stmt.executeUpdate(ADD_FLOODGATE_COLUMN_STMT);
101+
}
102+
103+
}
104+
}
105+
106+
private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException {
107+
try (ResultSet rs = metaData.getColumns(null, null, PREMIUM_TABLE, columnName)) {
108+
return !rs.next();
89109
}
90110
}
91111

@@ -97,7 +117,8 @@ public StoredProfile loadProfile(String name) {
97117
loadStmt.setString(1, name);
98118

99119
try (ResultSet resultSet = loadStmt.executeQuery()) {
100-
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
120+
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false,
121+
FloodgateState.FALSE, ""));
101122
}
102123
} catch (SQLException sqlEx) {
103124
log.error("Failed to query profile: {}", name, sqlEx);
@@ -124,15 +145,25 @@ public StoredProfile loadProfile(UUID uuid) {
124145

125146
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
126147
if (resultSet.next()) {
127-
long userId = resultSet.getInt(1);
148+
long userId = resultSet.getInt("UserID");
149+
150+
UUID uuid = Optional.ofNullable(resultSet.getString("UUID")).map(UUIDAdapter::parseId).orElse(null);
128151

129-
UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
152+
String name = resultSet.getString("Name");
153+
boolean premium = resultSet.getBoolean("Premium");
154+
int floodgateNum = resultSet.getInt("Floodgate");
155+
FloodgateState floodgate;
156+
157+
// if the player wasn't migrated to the new database format
158+
if (resultSet.wasNull()) {
159+
floodgate = FloodgateState.NOT_MIGRATED;
160+
} else {
161+
floodgate = FloodgateState.fromInt(floodgateNum);
162+
}
130163

131-
String name = resultSet.getString(3);
132-
boolean premium = resultSet.getBoolean(4);
133-
String lastIp = resultSet.getString(5);
134-
Instant lastLogin = resultSet.getTimestamp(6).toInstant();
135-
return Optional.of(new StoredProfile(userId, uuid, name, premium, lastIp, lastLogin));
164+
String lastIp = resultSet.getString("LastIp");
165+
Instant lastLogin = resultSet.getTimestamp("LastLogin").toInstant();
166+
return Optional.of(new StoredProfile(userId, uuid, name, premium, floodgate, lastIp, lastLogin));
136167
}
137168

138169
return Optional.empty();
@@ -150,9 +181,10 @@ public void save(StoredProfile playerProfile) {
150181
saveStmt.setString(1, uuid);
151182
saveStmt.setString(2, playerProfile.getName());
152183
saveStmt.setBoolean(3, playerProfile.isPremium());
153-
saveStmt.setString(4, playerProfile.getLastIp());
184+
saveStmt.setInt(4, playerProfile.getFloodgate().getValue());
185+
saveStmt.setString(5, playerProfile.getLastIp());
154186

155-
saveStmt.setLong(5, playerProfile.getRowId());
187+
saveStmt.setLong(6, playerProfile.getRowId());
156188
saveStmt.execute();
157189
}
158190
} else {
@@ -161,7 +193,9 @@ public void save(StoredProfile playerProfile) {
161193

162194
saveStmt.setString(2, playerProfile.getName());
163195
saveStmt.setBoolean(3, playerProfile.isPremium());
164-
saveStmt.setString(4, playerProfile.getLastIp());
196+
saveStmt.setBoolean(3, playerProfile.isPremium());
197+
saveStmt.setInt(4, playerProfile.getFloodgate().getValue());
198+
saveStmt.setString(5, playerProfile.getLastIp());
165199

166200
saveStmt.execute();
167201
try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) {
@@ -179,6 +213,14 @@ public void save(StoredProfile playerProfile) {
179213
}
180214
}
181215

216+
/**
217+
* SQLite has a slightly different syntax, so this will be overridden by SQLiteStorage
218+
* @return An SQL Statement to create the `premium` table
219+
*/
220+
protected String getCreateTableStmt() {
221+
return CREATE_TABLE_STMT;
222+
}
223+
182224
@Override
183225
public void close() {
184226
dataSource.close();

0 commit comments

Comments
 (0)