diff --git a/CHANGELOG.md b/CHANGELOG.md index 9240b11e..b470dcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ######1.4 * Added Bungee setAuthPlugin method +* Added nameChangeCheck * Multiple BungeeCord support ######1.3.1 diff --git a/README.md b/README.md index f8822a52..bc4c1245 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ So they don't need to enter passwords. This is also called auto login (auto-logi * Forge/Sponge message support * Premium UUID support * Forwards Skins +* Detect user name changed and will update the existing database record * BungeeCord support * Auto register new premium players * Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java index 541d051a..50c4753f 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; + import org.bukkit.Achievement; import org.bukkit.Effect; import org.bukkit.EntityEffect; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java index e22e2ba3..90ad55be 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java @@ -71,7 +71,7 @@ public class LoginSecurityHook implements BukkitAuthPlugin { } @Override - public boolean forceRegister(final Player player, final String password) { + public boolean forceRegister(Player player, String password) { DataManager dataManager = securityPlugin.data; UUID playerUUID = player.getUniqueId(); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java index 532d5089..e981dea9 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java @@ -1,7 +1,7 @@ package com.github.games647.fastlogin.bukkit.listener; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin; import com.github.games647.fastlogin.core.PlayerProfile; @@ -40,20 +40,31 @@ public class ProtocolSupportListener implements Listener { return; } - PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(username); - if (playerProfile != null) { - if (playerProfile.isPremium()) { - if (playerProfile.getUserId() != -1) { - startPremiumSession(username, loginStartEvent, true, playerProfile); + PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username); + if (profile != null) { + if (profile.isPremium()) { + if (profile.getUserId() != -1) { + startPremiumSession(username, loginStartEvent, true, profile); } - } else if (playerProfile.getUserId() == -1) { + } else if (profile.getUserId() == -1) { //user not exists in the db try { + if (plugin.getConfig().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + startPremiumSession(username, loginStartEvent, false, profile); + } + } + } + if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); if (premiumUUID != null) { plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username); - startPremiumSession(username, loginStartEvent, false, playerProfile); + startPremiumSession(username, loginStartEvent, false, profile); } } } catch (Exception ex) { @@ -75,8 +86,7 @@ public class ProtocolSupportListener implements Listener { } } - private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered - , PlayerProfile playerProfile) { + private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered, PlayerProfile playerProfile) { loginStartEvent.setOnlineMode(true); InetSocketAddress address = loginStartEvent.getAddress(); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java index 41b5d711..04f5a10d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java @@ -5,8 +5,8 @@ import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin; import com.github.games647.fastlogin.core.PlayerProfile; @@ -92,6 +92,17 @@ public class StartPacketListener extends PacketAdapter { } else if (profile.getUserId() == -1) { //user not exists in the db try { + if (plugin.getConfig().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false); + } + } + } + if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); if (premiumUUID != null) { diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java index a0662eb0..8a7019a0 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java @@ -38,6 +38,18 @@ public class AsyncPremiumCheck implements Runnable { } else if (profile.getUserId() == -1) { //user not exists in the db BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin(); + if (plugin.getConfiguration().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + connection.setOnlineMode(true); + plugin.getSession().put(connection, new LoginSession(username, false, profile)); + } + } + } + if (plugin.getConfiguration().getBoolean("autoRegister") && (authPlugin == null || !authPlugin.isRegistered(username))) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java index f04ffdef..57932c43 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java @@ -37,14 +37,9 @@ public class FastLoginBungee extends Plugin { private final Random random = new Random(); - private final ConcurrentMap pendingAutoRegister = CacheBuilder - .newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) - .build().asMap(); - private final ConcurrentMap session = CacheBuilder .newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) + .expireAfterWrite(5, TimeUnit.MINUTES) .build().asMap(); @Override diff --git a/core/src/main/java/com/github/games647/fastlogin/core/Storage.java b/core/src/main/java/com/github/games647/fastlogin/core/Storage.java index aa97b5ef..e44204f5 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/Storage.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/Storage.java @@ -46,9 +46,10 @@ public class Storage { public void createTables() throws SQLException { Connection con = null; + Statement createStmt = null; try { con = dataSource.getConnection(); - Statement statement = con.createStatement(); + createStmt = con.createStatement(); String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " (" + "UserID INTEGER PRIMARY KEY AUTO_INCREMENT, " + "UUID CHAR(36), " @@ -65,21 +66,23 @@ public class Storage { createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT"); } - statement.executeUpdate(createDataStmt); + createStmt.executeUpdate(createDataStmt); } finally { closeQuietly(con); + closeQuietly(createStmt); } } public PlayerProfile loadProfile(String name) { Connection con = null; + PreparedStatement loadStmt = null; + ResultSet resultSet = null; try { con = dataSource.getConnection(); - PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE - + " WHERE Name=? LIMIT 1"); - loadStatement.setString(1, name); + loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=?"); + loadStmt.setString(1, name); - ResultSet resultSet = loadStatement.executeQuery(); + resultSet = loadStmt.executeQuery(); if (resultSet.next()) { long userId = resultSet.getInt(1); @@ -104,6 +107,39 @@ public class Storage { core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx); } finally { closeQuietly(con); + closeQuietly(loadStmt); + closeQuietly(resultSet); + } + + return null; + } + + public PlayerProfile loadProfile(UUID uuid) { + Connection con = null; + PreparedStatement loadStmt = null; + ResultSet resultSet = null; + try { + con = dataSource.getConnection(); + loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=?"); + loadStmt.setString(1, uuid.toString().replace("-", "")); + + resultSet = loadStmt.executeQuery(); + if (resultSet.next()) { + long userId = resultSet.getInt(1); + + String name = resultSet.getString(3); + boolean premium = resultSet.getBoolean(4); + String lastIp = resultSet.getString(5); + long lastLogin = resultSet.getTimestamp(6).getTime(); + PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin); + return playerProfile; + } + } catch (SQLException sqlEx) { + core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx); + } finally { + closeQuietly(con); + closeQuietly(loadStmt); + closeQuietly(resultSet); } return null; @@ -111,46 +147,69 @@ public class Storage { public boolean save(PlayerProfile playerProfile) { Connection con = null; + PreparedStatement updateStmt = null; + PreparedStatement saveStmt = null; + + ResultSet generatedKeys = null; try { con = dataSource.getConnection(); UUID uuid = playerProfile.getUuid(); - if (playerProfile.getUserId() == -1) { - PreparedStatement saveStatement = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE - + " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + //User was authenticated with a premium authentication, so it's possible that the player is premium + if (uuid != null) { + updateStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE + + " SET NAME=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP" + + " WHERE UUID=? AND PREMIUM=1"); - if (uuid == null) { - saveStatement.setString(1, null); - } else { - saveStatement.setString(1, uuid.toString().replace("-", "")); + updateStmt.setString(1, playerProfile.getPlayerName()); + updateStmt.setString(2, playerProfile.getLastIp()); + updateStmt.setString(3, uuid.toString().replace("-", "")); + + int affectedRows = updateStmt.executeUpdate(); + if (affectedRows > 0) { + //username changed and we updated the existing database record + //so we don't need to run an insert + return true; + } } - saveStatement.setString(2, playerProfile.getPlayerName()); - saveStatement.setBoolean(3, playerProfile.isPremium()); - saveStatement.setString(4, playerProfile.getLastIp()); - saveStatement.execute(); + saveStmt = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE + + " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) " + , Statement.RETURN_GENERATED_KEYS); - ResultSet generatedKeys = saveStatement.getGeneratedKeys(); + if (uuid == null) { + saveStmt.setString(1, null); + } else { + saveStmt.setString(1, uuid.toString().replace("-", "")); + } + + saveStmt.setString(2, playerProfile.getPlayerName()); + saveStmt.setBoolean(3, playerProfile.isPremium()); + saveStmt.setString(4, playerProfile.getLastIp()); + + saveStmt.execute(); + + generatedKeys = saveStmt.getGeneratedKeys(); if (generatedKeys != null && generatedKeys.next()) { playerProfile.setUserId(generatedKeys.getInt(1)); } } else { - PreparedStatement saveStatement = con.prepareStatement("UPDATE " + PREMIUM_TABLE + saveStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE + " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?"); if (uuid == null) { - saveStatement.setString(1, null); + saveStmt.setString(1, null); } else { - saveStatement.setString(1, uuid.toString().replace("-", "")); + saveStmt.setString(1, uuid.toString().replace("-", "")); } - saveStatement.setString(2, playerProfile.getPlayerName()); - saveStatement.setBoolean(3, playerProfile.isPremium()); - saveStatement.setString(4, playerProfile.getLastIp()); + saveStmt.setString(2, playerProfile.getPlayerName()); + saveStmt.setBoolean(3, playerProfile.isPremium()); + saveStmt.setString(4, playerProfile.getLastIp()); - saveStatement.setLong(5, playerProfile.getUserId()); - saveStatement.execute(); + saveStmt.setLong(5, playerProfile.getUserId()); + saveStmt.execute(); } return true; @@ -158,6 +217,9 @@ public class Storage { core.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex); } finally { closeQuietly(con); + closeQuietly(updateStmt); + closeQuietly(saveStmt); + closeQuietly(generatedKeys); } return false; @@ -167,12 +229,12 @@ public class Storage { dataSource.close(); } - private void closeQuietly(Connection con) { - if (con != null) { + private void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { try { - con.close(); - } catch (SQLException sqlEx) { - core.getLogger().log(Level.SEVERE, "Failed to close connection", sqlEx); + closeable.close(); + } catch (Exception closeEx) { + core.getLogger().log(Level.SEVERE, "Failed to close connection", closeEx); } } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 38996455..c8c689d2 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -40,6 +40,50 @@ autoRegister: false # This feature requires Cauldron, Spigot or a fork of Spigot (PaperSpigot, TacoSpigot) premiumUuid: false +# This will make an additional check (only for player names which are not in the database) against the mojang servers +# in order to get the premium UUID. If that premium UUID is in the database, we can assume on sucessful login that the +# player changed it's username and we just update the name in the database. +# Examples: +# #### Case 1 +# nameChangeCheck = false ----- autoRegister = false +# +# Player logins as cracked until the player invoked the command /premium. Then we could override the existing database +# record. +# +# #### Case 2 +# +# nameChangeCheck = true ----- autoRegister = false +# +# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in +# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username. +# If it's not in the database, it's a new player and **could be a cracked player**. So we just use a offline mode +# authentication for this player. +# +# **Limitation**: Cracked players who uses the new username of a paid account cannot join the server if the database +# contains the old name. (Example: The owner of the paid account no longer plays on the server, but changed the username +# in the meanwhile). +# +# #### Case 3 +# +# nameChangeCheck = false ----- autoRegister = true +# +# We will always request a premium authentication if the username is unknown to us, but is in use by a paid minecraft +# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because +# it request a premium authentication which are completely new to us, that even the premium UUID is not in our database. +# +# **Limitation**: see below +# +# #### Case 4 +# +# nameChangeCheck = true ----- autoRegister = true +# +# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that +# fastlogin receives the premium UUID and can update the database record. +# +# **Limitation from autoRegister**: New offline players who uses the username of an existing minecraft cannot join the +# server. +nameChangeCheck: false + # If your players have a premium account and a skin associated to their account, this plugin # can download the data and set it to the online player. # diff --git a/out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class b/out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class new file mode 100644 index 00000000..d7f45d1b Binary files /dev/null and b/out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class differ diff --git a/out/production/main2/com/github/games647/fastlogin/core/LoginSession.class b/out/production/main2/com/github/games647/fastlogin/core/LoginSession.class new file mode 100644 index 00000000..e99a1e4e Binary files /dev/null and b/out/production/main2/com/github/games647/fastlogin/core/LoginSession.class differ diff --git a/out/production/main2/com/github/games647/fastlogin/core/MojangApiConnector.class b/out/production/main2/com/github/games647/fastlogin/core/MojangApiConnector.class new file mode 100644 index 00000000..82d4ee08 Binary files /dev/null and b/out/production/main2/com/github/games647/fastlogin/core/MojangApiConnector.class differ diff --git a/out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class b/out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class new file mode 100644 index 00000000..f54b7a91 Binary files /dev/null and b/out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class differ diff --git a/out/production/main2/com/github/games647/fastlogin/core/Storage.class b/out/production/main2/com/github/games647/fastlogin/core/Storage.class new file mode 100644 index 00000000..fd75f7ce Binary files /dev/null and b/out/production/main2/com/github/games647/fastlogin/core/Storage.class differ