Encapsulate floodgate hooks

Related #619
Related #620
This commit is contained in:
games647
2021-10-05 15:35:43 +02:00
parent 11c91e6428
commit a3bf875976
9 changed files with 157 additions and 107 deletions

View File

@ -35,6 +35,7 @@ import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSup
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
@ -42,7 +43,6 @@ import io.papermc.lib.PaperLib;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -69,6 +69,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
private BungeeManager bungeeManager;
private final BukkitScheduler scheduler;
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
private FloodgateService floodgateService;
private PremiumPlaceholder premiumPlaceholder;
@ -88,13 +89,10 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
setEnabled(false);
return;
}
// Check Floodgate config values
if (!isValidFloodgateConfigString("autoLoginFloodgate")
|| !isValidFloodgateConfigString("allowFloodgateNameConflict")) {
setEnabled(false);
return;
}
if (!initializeFloodgate()) {
setEnabled(false);
}
bungeeManager = new BungeeManager(this);
bungeeManager.initialize();
@ -146,6 +144,20 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
dependencyWarnings();
}
private boolean initializeFloodgate() {
if (getServer().getPluginManager().getPlugin("Floodgate") != null) {
floodgateService = new FloodgateService(core);
}
// Check Floodgate config values
if (!floodgateService.isValidFloodgateConfigString("autoLoginFloodgate")
|| !floodgateService.isValidFloodgateConfigString("allowFloodgateNameConflict")) {
return false;
}
return true;
}
@Override
public void onDisable() {
loginSession.clear();
@ -251,30 +263,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
receiver.sendMessage(message);
}
/**
* Checks if a config entry (related to Floodgate) is valid. <br>
* Writes to Log if the value is invalid.
* <p>
* This should be used for:
* <ul>
* <li>allowFloodgateNameConflict
* <li>autoLoginFloodgate
* <li>autoRegisterFloodgate
* </ul>
* </p>
*
* @param key the key of the entry in config.yml
* @return <b>true</b> if the entry's value is "true", "false", or "linked"
*/
private boolean isValidFloodgateConfigString(String key) {
String value = core.getConfig().get(key).toString().toLowerCase(Locale.ENGLISH);
if (!value.equals("true") && !value.equals("linked") && !value.equals("false") && !value.equals("no-conflict")) {
logger.error("Invalid value detected for {} in FastLogin/config.yml.", key);
return false;
}
return true;
}
/**
* Checks if a plugin is installed on the server
* @param name the name of the plugin
@ -286,6 +274,11 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return Bukkit.getServer().getPluginManager().getPlugin(name) != null;
}
@Override
public FloodgateService getFloodgateService() {
return floodgateService;
}
/**
* Send warning messages to log if incompatible plugins are used
*/

View File

@ -29,6 +29,7 @@ import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.task.FloodgateAuthTask;
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -38,9 +39,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.bukkit.event.player.PlayerQuitEvent;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
/**
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
@ -69,37 +69,38 @@ public class ConnectionListener implements Listener {
Player player = joinEvent.getPlayer();
Bukkit.getScheduler().runTaskLater(plugin, () -> {
// session exists so the player is ready for force login
// cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already
// having the login session from the login process
BukkitLoginSession session = plugin.getSession(player.getAddress());
boolean isFloodgateLogin = false;
if (Bukkit.getServer().getPluginManager().isPluginEnabled("floodgate")) {
FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(player.getUniqueId());
if (floodgatePlayer != null) {
isFloodgateLogin = true;
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer);
Bukkit.getScheduler().runTaskAsynchronously(plugin, floodgateAuthTask);
}
}
if (!isFloodgateLogin) {
if (session == null) {
String sessionId = plugin.getSessionId(player.getAddress());
plugin.getLog().info("No on-going login session for player: {} with ID {}", player, sessionId);
} else {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
}
plugin.getBungeeManager().markJoinEventFired(player);
delayForceLogin(player);
// delay the login process to let auth plugins initialize the player
// Magic number however as there is no direct event from those plugins
}, DELAY_LOGIN);
}
private void delayForceLogin(Player player) {
// session exists so the player is ready for force login
// cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already
// having the login session from the login process
BukkitLoginSession session = plugin.getSession(player.getAddress());
FloodgateService floodgateService = plugin.getFloodgateService();
if (floodgateService != null) {
FloodgatePlayer floodgatePlayer = floodgateService.getFloodgatePlayer(player.getUniqueId());
if (floodgatePlayer != null) {
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer);
Bukkit.getScheduler().runTaskAsynchronously(plugin, floodgateAuthTask);
return;
}
}
if (session == null) {
String sessionId = plugin.getSessionId(player.getAddress());
plugin.getLog().info("No on-going login session for player: {} with ID {}", player, sessionId);
} else {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
plugin.getBungeeManager().markJoinEventFired(player);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();

View File

@ -54,7 +54,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
public NameCheckTask(FastLoginBukkit plugin, Random random, Player player, PacketEvent packetEvent,
String username, PublicKey publicKey) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getCore().getFloodgateService());
this.plugin = plugin;
this.packetEvent = packetEvent;

View File

@ -33,6 +33,7 @@ import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask;
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
import com.github.games647.fastlogin.core.RateLimiter;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.google.common.base.Throwables;
@ -56,7 +57,6 @@ import net.md_5.bungee.connection.LoginResult.Property;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -184,8 +184,9 @@ public class ConnectListener implements Listener {
ProxiedPlayer player = serverConnectedEvent.getPlayer();
Server server = serverConnectedEvent.getServer();
if (plugin.isPluginInstalled("floodgate")) {
FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(player.getUniqueId());
FloodgateService floodgateService = plugin.getFloodgateService();
if (floodgateService != null) {
FloodgatePlayer floodgatePlayer = floodgateService.getFloodgatePlayer(player.getUniqueId());
if (floodgatePlayer != null) {
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer, server);
plugin.getScheduler().runAsync(floodgateAuthTask);

View File

@ -29,6 +29,7 @@ import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.task.AsyncToggleMessage;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
@ -36,9 +37,6 @@ import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
@ -118,13 +116,15 @@ public class PluginMessageListener implements Listener {
}
private void onSuccessMessage(ProxiedPlayer forPlayer) {
//check if player is using Floodgate
FloodgatePlayer floodgatePlayer = null;
if (plugin.isPluginInstalled("floodgate")) {
floodgatePlayer = FloodgateApi.getInstance().getPlayer(forPlayer.getUniqueId());
boolean shouldPersist = forPlayer.getPendingConnection().isOnlineMode();
FloodgateService floodgateService = plugin.getFloodgateService();
if (!shouldPersist && floodgateService != null) {
// always save floodgate players to lock this username
shouldPersist = floodgateService.isFloodgatePlayer(forPlayer.getUniqueId());
}
if (forPlayer.getPendingConnection().isOnlineMode() || floodgatePlayer != null){
if (shouldPersist) {
//bukkit module successfully received and force logged in the user
//update only on success to prevent corrupt data
BungeeLoginSession loginSession = plugin.getSession().get(forPlayer.getPendingConnection());

View File

@ -27,23 +27,56 @@ package com.github.games647.fastlogin.core.hooks;
import com.github.games647.craftapi.model.Profile;
import com.github.games647.craftapi.resolver.RateLimitException;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.LoginSource;
import java.io.IOException;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public class FloodgateHook<P extends C, C, S extends LoginSource> {
public class FloodgateService {
private final FastLoginCore<P, C, ?> core;
private final FastLoginCore<?, ?, ?> core;
public FloodgateHook(FastLoginCore<P, C, ?> core) {
public FloodgateService(FastLoginCore<?, ?, ?> core) {
this.core = core;
}
/**
* Checks if a config entry (related to Floodgate) is valid. <br>
* Writes to Log if the value is invalid.
* <p>
* This should be used for:
* <ul>
* <li>allowFloodgateNameConflict
* <li>autoLoginFloodgate
* <li>autoRegisterFloodgate
* </ul>
* </p>
*
* @param key the key of the entry in config.yml
* @return <b>true</b> if the entry's value is "true", "false", or "linked"
*/
public boolean isValidFloodgateConfigString(String key) {
String value = core.getConfig().get(key).toString().toLowerCase(Locale.ENGLISH);
if (!value.equals("true") && !value.equals("linked") && !value.equals("false") && !value.equals("no-conflict")) {
core.getPlugin().getLog().error("Invalid value detected for {} in FastLogin/config.yml.", key);
return false;
}
return true;
}
public boolean isUsernameForbidden(StoredProfile profile) {
String playerPrefix = FloodgateApi.getInstance().getPlayerPrefix();
return profile.getName().startsWith(playerPrefix) && !playerPrefix.isEmpty();
}
/**
* Check if the player's name conflicts an existing Java player's name, and
* kick them if it does
@ -55,10 +88,10 @@ public class FloodgateHook<P extends C, C, S extends LoginSource> {
String allowConflict = core.getConfig().get("allowFloodgateNameConflict").toString().toLowerCase();
// check if the Bedrock player is linked to a Java account
boolean isLinked = ((FloodgatePlayer) floodgatePlayer).getLinkedPlayer() != null;
boolean isLinked = floodgatePlayer.getLinkedPlayer() != null;
if (allowConflict.equals("false")
|| allowConflict.equals("linked") && !isLinked) {
if ("false".equals(allowConflict)
|| "linked".equals(allowConflict) && !isLinked) {
// check for conflicting Premium Java name
Optional<Profile> premiumUUID = Optional.empty();
@ -66,13 +99,13 @@ public class FloodgateHook<P extends C, C, S extends LoginSource> {
premiumUUID = core.getResolver().findProfile(username);
} catch (IOException | RateLimitException e) {
core.getPlugin().getLog().error(
"Could not check wether Floodgate Player {}'s name conflicts a premium Java player's name.",
"Could not check whether Floodgate Player {}'s name conflicts a premium Java player's name.",
username);
try {
source.kick("Could not check if your name conflicts an existing premium Java account's name.\n"
+ "This is usually a serverside error.");
} catch (Exception e1) {
core.getPlugin().getLog().error("Could not kick Player {}", username);
} catch (Exception ex) {
core.getPlugin().getLog().error("Could not kick Player {}", username, ex);
}
}
@ -81,8 +114,8 @@ public class FloodgateHook<P extends C, C, S extends LoginSource> {
username);
try {
source.kick("Your name conflicts an existing premium Java account's name");
} catch (Exception e) {
core.getPlugin().getLog().error("Could not kick Player {}", username);
} catch (Exception ex) {
core.getPlugin().getLog().error("Could not kick Player {}", username, ex);
}
}
} else {
@ -99,14 +132,25 @@ public class FloodgateHook<P extends C, C, S extends LoginSource> {
* @return FloodgatePlayer if found, null otherwise
*/
public FloodgatePlayer getFloodgatePlayer(String username) {
if (core.getPlugin().isPluginInstalled("floodgate")) {
for (FloodgatePlayer floodgatePlayer : FloodgateApi.getInstance().getPlayers()) {
if (floodgatePlayer.getUsername().equals(username)) {
return floodgatePlayer;
}
for (FloodgatePlayer floodgatePlayer : FloodgateApi.getInstance().getPlayers()) {
if (floodgatePlayer.getUsername().equals(username)) {
return floodgatePlayer;
}
}
return null;
}
public FloodgatePlayer getFloodgatePlayer(UUID uuid) {
return FloodgateApi.getInstance().getPlayer(uuid);
}
public boolean isFloodgatePlayer(UUID uuid) {
return getFloodgatePlayer(uuid) != null;
}
public boolean isFloodgateConnection(String username) {
return getFloodgatePlayer(username) != null;
}
}

View File

@ -27,13 +27,13 @@ package com.github.games647.fastlogin.core.shared;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
import com.github.games647.fastlogin.core.storage.MySQLStorage;
import com.github.games647.fastlogin.core.storage.SQLStorage;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.RateLimiter;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
import com.github.games647.fastlogin.core.storage.MySQLStorage;
import com.github.games647.fastlogin.core.storage.SQLStorage;
import com.github.games647.fastlogin.core.storage.SQLiteStorage;
import com.google.common.net.HostAndPort;
import com.zaxxer.hikari.HikariConfig;

View File

@ -29,26 +29,25 @@ import com.github.games647.craftapi.model.Profile;
import com.github.games647.craftapi.resolver.RateLimitException;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.hooks.FloodgateHook;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.util.Optional;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import net.md_5.bungee.config.Configuration;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
protected final FastLoginCore<P, C, ?> core;
protected final AuthPlugin<P> authHook;
private final FloodgateHook<P, C, ?> floodgateHook;
private final FloodgateService floodgateService;
public JoinManagement(FastLoginCore<P, C, ?> core, AuthPlugin<P> authHook) {
public JoinManagement(FastLoginCore<P, C, ?> core, AuthPlugin<P> authHook, FloodgateService floodService) {
this.core = core;
this.authHook = authHook;
this.floodgateHook = new FloodgateHook<>(core);
this.floodgateService = floodService;
}
public void onLogin(String username, S source) {
@ -59,12 +58,14 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
}
//check if the player is connecting through Floodgate
FloodgatePlayer floodgatePlayer = floodgateHook.getFloodgatePlayer(username);
if (floodgatePlayer != null) {
floodgateHook.checkFloodgateNameConflict(username, source, floodgatePlayer);
return;
if (floodgateService != null) {
if (floodgateService.isFloodgateConnection(username)) {
floodgateService.checkFloodgateNameConflict(username, source, floodgatePlayer);
// skip flow for any floodgate player
return;
}
}
callFastLoginPreLoginEvent(username, source, profile);
Configuration config = core.getConfig();
@ -77,12 +78,9 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
core.getPlugin().getLog().info("Requesting premium login for registered player: {}", username);
requestPremiumLogin(source, profile, username, true);
} else {
if (profile.getName().startsWith(FloodgateApi.getInstance().getPlayerPrefix())&&!FloodgateApi.getInstance().getPlayerPrefix().isEmpty()) {
core.getPlugin().getLog().info("Floodgate Prefix detected on cracked player");
source.kick("Your username contains illegal characters");
return;
if (isValidUsername(source, profile)) {
startCrackedSession(source, profile, username);
}
startCrackedSession(source, profile, username);
}
} else {
if (core.getPendingLogin().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
@ -120,6 +118,16 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
}
}
protected boolean isValidUsername(LoginSource source, StoredProfile profile) throws Exception {
if (floodgateService != null && floodgateService.isUsernameForbidden(profile)) {
core.getPlugin().getLog().info("Floodgate Prefix detected on cracked player");
source.kick("Your username contains illegal characters");
return false;
}
return true;
}
private boolean checkPremiumName(S source, String username, StoredProfile profile) throws Exception {
core.getPlugin().getLog().info("GameProfile {} uses a premium username", username);
if (core.getConfig().get("autoRegister", false) && (authHook == null || !authHook.isRegistered(username))) {

View File

@ -26,6 +26,7 @@
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.github.games647.fastlogin.core.hooks.FloodgateService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
@ -53,6 +54,8 @@ public interface PlatformPlugin<C> {
}
}
FloodgateService getFloodgateService();
default ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(getName() + " Pool Thread #%1$d")