Migrate to session manager

This commit is contained in:
games647
2021-03-15 14:04:33 +01:00
parent e88f2e7f8e
commit 13bc92f6c3
13 changed files with 169 additions and 88 deletions

View File

@ -0,0 +1,23 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.core.SessionManager;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class BukkitSessionManager extends SessionManager<PlayerQuitEvent, InetSocketAddress, BukkitLoginSession>
implements Listener {
@EventHandler
@Override
public void onPlayQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@ -10,29 +10,30 @@ import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSup
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook; import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
import com.github.games647.fastlogin.core.CommonUtil; import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus; import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.FastLoginCore; import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin; import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import io.papermc.lib.PaperLib; import io.papermc.lib.PaperLib;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/** /**
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication. * This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
*/ */
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> { public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang) private final BukkitSessionManager sessionManager = new BukkitSessionManager();
private final ConcurrentMap<String, BukkitLoginSession> loginSession = CommonUtil.buildCache(1, -1);
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>(); private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
private final Logger logger; private final Logger logger;
@ -108,7 +109,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
@Override @Override
public void onDisable() { public void onDisable() {
loginSession.clear();
premiumPlayers.clear(); premiumPlayers.clear();
if (core != null) { if (core != null) {
@ -125,48 +125,36 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return core; return core;
} }
/**
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
* account)
*
* @return a thread-safe loginSession map
*/
public ConcurrentMap<String, BukkitLoginSession> getLoginSessions() {
return loginSession;
}
public BukkitLoginSession getSession(InetSocketAddress addr) {
String id = getSessionId(addr);
return loginSession.get(id);
}
public String getSessionId(InetSocketAddress addr) {
return addr.getAddress().getHostAddress() + ':' + addr.getPort();
}
public void putSession(InetSocketAddress addr, BukkitLoginSession session) {
String id = getSessionId(addr);
loginSession.put(id, session);
}
public void removeSession(InetSocketAddress addr) {
String id = getSessionId(addr);
loginSession.remove(id);
}
public Map<UUID, PremiumStatus> getPremiumPlayers() {
return premiumPlayers;
}
/** /**
* Fetches the premium status of an online player. * Fetches the premium status of an online player.
* *
* @param onlinePlayer * @param onlinePlayer
* @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send * @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent). * us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
* @deprecated this method could be removed in future versions and exists only as a temporarily solution
*/ */
@Deprecated
public PremiumStatus getStatus(UUID onlinePlayer) { public PremiumStatus getStatus(UUID onlinePlayer) {
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN); StoredProfile playSession = sessionManager.getPlaySession(onlinePlayer);
return Optional.ofNullable(playSession).map(profile -> {
if (profile.isPremium())
return PremiumStatus.PREMIUM;
return PremiumStatus.CRACKED;
}).orElse(PremiumStatus.UNKNOWN);
}
/**
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
* account)
*
* @return a thread-safe loginSession map
*/
public BukkitSessionManager getSessionManager() {
return sessionManager;
}
public Map<UUID, PremiumStatus> getPremiumPlayers() {
return premiumPlayers;
} }
/** /**

View File

@ -1,20 +1,22 @@
package com.github.games647.fastlogin.bukkit.hook; package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin; import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.events.RestoreSessionEvent; import fr.xephi.authme.events.RestoreSessionEvent;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams; import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod; import fr.xephi.authme.process.register.executors.RegistrationMethod;
import java.lang.reflect.Field;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import java.lang.reflect.Field;
/** /**
* GitHub: https://github.com/Xephi/AuthMeReloaded/ * GitHub: https://github.com/Xephi/AuthMeReloaded/
* <p> * <p>
@ -50,8 +52,8 @@ public class AuthMeHook implements AuthPlugin<Player>, Listener {
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) { public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
Player player = restoreSessionEvent.getPlayer(); Player player = restoreSessionEvent.getPlayer();
BukkitLoginSession session = plugin.getSession(player.getAddress()); StoredProfile session = plugin.getSessionManager().getPlaySession(player.getUniqueId());
if (session != null && session.isVerified()) { if (session != null && session.isPremium()) {
restoreSessionEvent.setCancelled(true); restoreSessionEvent.setCancelled(true);
} }
} }

View File

@ -101,7 +101,7 @@ public class BungeeListener implements PluginMessageListener {
private void startLoginTaskIfReady(Player player, BukkitLoginSession session) { private void startLoginTaskIfReady(Player player, BukkitLoginSession session) {
session.setVerified(true); session.setVerified(true);
plugin.putSession(player.getAddress(), session); plugin.getSessionManager().startLoginSession(player.getAddress(), session);
// only start a new login task if the join event fired earlier. This event then didn // only start a new login task if the join event fired earlier. This event then didn
boolean result = plugin.getBungeeManager().didJoinEventFired(player); boolean result = plugin.getBungeeManager().didJoinEventFired(player);

View File

@ -44,11 +44,8 @@ public class ConnectionListener implements Listener {
// session exists so the player is ready for force login // session exists so the player is ready for force login
// cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already // cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already
// having the login session from the login process // having the login session from the login process
BukkitLoginSession session = plugin.getSession(player.getAddress()); BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session == null) { 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); Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask); Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
} }

View File

@ -27,7 +27,7 @@ public class PaperPreLoginListener implements Listener {
} }
// event gives us only IP, not the port, so we need to loop through all the sessions // event gives us only IP, not the port, so we need to loop through all the sessions
for (BukkitLoginSession session : plugin.getLoginSessions().values()) { for (BukkitLoginSession session : plugin.getSessionManager().getLoginSessions().values()) {
if (!event.getName().equals(session.getUsername())) { if (!event.getName().equals(session.getUsername())) {
continue; continue;
} }

View File

@ -74,7 +74,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
byte[] verify = source.getVerifyToken(); byte[] verify = source.getVerifyToken();
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile); BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
plugin.putSession(player.getAddress(), playerSession); plugin.getSessionManager().startLoginSession(player.getAddress(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player //cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) { synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true); packetEvent.setCancelled(true);
@ -84,6 +84,6 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
@Override @Override
public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) { public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile); BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.putSession(player.getAddress(), loginSession); plugin.getSessionManager().startLoginSession(player.getAddress(), loginSession);
} }
} }

View File

@ -79,7 +79,7 @@ public class ProtocolLibListener extends PacketAdapter {
String sessionKey = player.getAddress().toString(); String sessionKey = player.getAddress().toString();
//remove old data every time on a new login in order to keep the session only for one person //remove old data every time on a new login in order to keep the session only for one person
plugin.removeSession(player.getAddress()); plugin.getSessionManager().endLoginSession(player.getAddress());
//player.getName() won't work at this state //player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket(); PacketContainer packet = packetEvent.getPacket();

View File

@ -1,6 +1,5 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib; package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
@ -9,6 +8,7 @@ import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.craftapi.model.skin.Textures; import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@ -16,8 +16,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.player.PlayerLoginEvent.Result;
import java.lang.reflect.InvocationTargetException;
public class SkinApplyListener implements Listener { public class SkinApplyListener implements Listener {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass(); private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
@ -40,28 +38,15 @@ public class SkinApplyListener implements Listener {
//go through every session, because player.getAddress is null //go through every session, because player.getAddress is null
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough //loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
for (BukkitLoginSession session : plugin.getLoginSessions().values()) { BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session.getUsername().equals(player.getName())) { if (session.getUsername().equals(player.getName())) {
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature())); session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
break;
}
} }
} }
private void applySkin(Player player, String skinData, String signature) { private void applySkin(Player player, String skinData, String signature) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player); WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature); WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
try { gameProfile.getProperties().put(Textures.KEY, skin);
gameProfile.getProperties().put(Textures.KEY, skin);
} catch (ClassCastException castException) {
//Cauldron, MCPC, Thermos, ...
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
try {
MethodUtils.invokeMethod(map, "put", new Object[]{Textures.KEY, skin.getHandle()});
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
plugin.getLog().error("Error setting premium skin of: {}", player, ex);
}
}
} }
} }

View File

@ -14,10 +14,7 @@ import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver; import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.entity.Player;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -31,6 +28,11 @@ import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Client.START; import static com.comphenix.protocol.PacketType.Login.Client.START;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT; import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
@ -59,7 +61,7 @@ public class VerifyResponseTask implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
BukkitLoginSession session = plugin.getSession(player.getAddress()); BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session == null) { if (session == null) {
disconnect("invalid-request", true disconnect("invalid-request", true
, "GameProfile {0} tried to send encryption response at invalid state", player.getAddress()); , "GameProfile {0} tried to send encryption response at invalid state", player.getAddress());

View File

@ -48,7 +48,7 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
InetSocketAddress address = loginStartEvent.getAddress(); InetSocketAddress address = loginStartEvent.getAddress();
//remove old data every time on a new login in order to keep the session only for one person //remove old data every time on a new login in order to keep the session only for one person
plugin.removeSession(address); plugin.getSessionManager().endLoginSession(address);
super.onLogin(username, new ProtocolLoginSource(loginStartEvent)); super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
} }
@ -56,13 +56,13 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
@EventHandler @EventHandler
public void onConnectionClosed(ConnectionCloseEvent closeEvent) { public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
InetSocketAddress address = closeEvent.getConnection().getAddress(); InetSocketAddress address = closeEvent.getConnection().getAddress();
plugin.removeSession(address); plugin.getSessionManager().endLoginSession(address);
} }
@EventHandler @EventHandler
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) { public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
InetSocketAddress address = profileCompleteEvent.getAddress(); InetSocketAddress address = profileCompleteEvent.getAddress();
BukkitLoginSession session = plugin.getSession(address); BukkitLoginSession session = plugin.getSessionManager().getLoginSession(address);
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) { if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
session.setVerified(true); session.setVerified(true);
@ -91,12 +91,12 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
plugin.getCore().getPendingLogin().put(ip + username, new Object()); plugin.getCore().getPendingLogin().put(ip + username, new Object());
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile); BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.putSession(source.getAddress(), playerSession); plugin.getSessionManager().startLoginSession(source.getAddress(), playerSession);
} }
@Override @Override
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) { public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile); BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.putSession(source.getAddress(), loginSession); plugin.getSessionManager().startLoginSession(source.getAddress(), loginSession);
} }
} }

View File

@ -0,0 +1,23 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.SessionManager;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class BungeeSessionManager extends SessionManager<PlayerDisconnectEvent, PendingConnection, BungeeLoginSession>
implements Listener {
//todo: memory leak on cancelled login event
@EventHandler
public void onPlayQuit(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@ -0,0 +1,61 @@
package com.github.games647.fastlogin.core;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.google.common.collect.MapMaker;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
/**
* Manages player connection sessions. Login sessions that are only valid through the login process and play
* sessions that hold the stored profile object after the login process is finished and until the player leaves the
* server (Spigot) or proxy (BungeeCord).
*
* @param <C> connection object
* @param <S> platform dependent login session
*/
public abstract class SessionManager<E, C, S extends LoginSession> {
// 1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
// these login sessions are only meant for during the login process not be used after
private final ConcurrentMap<C, S> loginSessions = CommonUtil.buildCache(1, 0);
private final ConcurrentMap<UUID, StoredProfile> playSessions = new MapMaker().makeMap();
public void startLoginSession(C connectionId, S session) {
loginSessions.put(connectionId, session);
}
public S getLoginSession(C connectionId) {
return loginSessions.get(connectionId);
}
public void endLoginSession(C connectionId) {
loginSessions.remove(connectionId);
}
public ConcurrentMap<C, S> getLoginSessions() {
return loginSessions;
}
public S promoteSession(C connectionId, UUID playerId) {
S loginSession = loginSessions.remove(connectionId);
StoredProfile profile = loginSession.getProfile();
playSessions.put(playerId, profile);
return loginSession;
}
public StoredProfile getPlaySession(UUID playerId) {
return playSessions.get(playerId);
}
public void endPlaySession(UUID playerId) {
playSessions.remove(playerId);
}
public abstract void onPlayQuit(E quitEvent);
public void clear() {
loginSessions.clear();
playSessions.clear();
}
}