Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public void onEnable() {

@Override
public void onDisable() {
proxyBridge.shutdown();
// BungeeCord also calls onDisable when onEnable failed, so proxyBridge may be null
if (proxyBridge != null) {
proxyBridge.shutdown();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import fr.xephi.authme.bungee.premium.BungeePremiumOnlineModeHandler;
import fr.xephi.authme.bungee.premium.BungeePremiumVerificationManager;
import fr.xephi.authme.bungee.premium.PremiumVerificationManager;
import fr.xephi.authme.bungee.premium.PremiumVerificationManagers;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.ProxyServer;
Expand All @@ -13,7 +14,7 @@
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
Expand Down Expand Up @@ -71,7 +72,7 @@ public final class BungeeProxyBridge implements Listener {
// Players with a pending premium verification (ran /premium but not yet confirmed via reconnect)
private volatile Set<String> pendingPremiumUsernames = ConcurrentHashMap.newKeySet();
private final BungeePremiumOnlineModeHandler premiumOnlineModeHandler;
private final BungeePremiumVerificationManager premiumVerificationManager;
private final PremiumVerificationManager premiumVerificationManager;
private final ScheduledExecutorService retryScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "authme-bungee-retry");
t.setDaemon(true);
Expand All @@ -87,7 +88,7 @@ public final class BungeeProxyBridge implements Listener {
this.authenticationStore = authenticationStore;
this.premiumOnlineModeHandler = new BungeePremiumOnlineModeHandler(this::requiresPremiumVerification);
this.premiumVerificationManager =
new BungeePremiumVerificationManager(proxyServer, logger,
PremiumVerificationManagers.create(proxyServer, logger,
this::requiresPremiumVerification, this::isPendingPremiumVerification,
this::clearPendingPremiumVerification,
() -> this.configuration.keepOfflineUuidCompatibility());
Expand Down Expand Up @@ -174,8 +175,10 @@ void registerChannels() {
}

@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerHandshake(PlayerHandshakeEvent event) {
public void onPreLogin(PreLoginEvent event) {
if (!configuration.keepOfflineUuidCompatibility()) {
// PlayerHandshakeEvent runs before LOGIN_START, so the connection name is still null there.
// PreLoginEvent runs after LOGIN_START and before Bungee chooses the encryption path.
premiumOnlineModeHandler.enableOnlineModeIfRequired(event.getConnection());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.util.function.Predicate;
import java.util.logging.Logger;

public final class BungeePremiumVerificationManager {
public final class BungeePremiumVerificationManager implements PremiumVerificationManager {

private final ProxyServer proxyServer;
private final Logger logger;
Expand All @@ -35,10 +35,12 @@ public BungeePremiumVerificationManager(ProxyServer proxyServer, Logger logger,
this.loginVerifier = new ProxyPremiumLoginVerifier("authme-bungee-premium", this.logger::warning);
}

@Override
public void register() {
refreshRegistration();
}

@Override
public void refreshRegistration() {
if (!keepOfflineUuidCompatibility.getAsBoolean()) {
unregisterPacketListener();
Expand All @@ -58,14 +60,17 @@ public void refreshRegistration() {
logger.info("Registered PacketEvents premium verification on the Bungee proxy");
}

@Override
public UUID getVerifiedPremiumUuid(String normalizedName) {
return loginVerifier.getVerifiedUuid(normalizedName);
}

@Override
public void clearVerifiedPremium(String normalizedName) {
loginVerifier.clearVerified(normalizedName);
}

@Override
public void shutdown() {
unregisterPacketListener();
loginVerifier.shutdown();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fr.xephi.authme.bungee.premium;

import java.util.UUID;

/**
* No-op implementation used when PacketEvents is not available on the proxy: the AuthMeBungee
* plugin keeps working, but players are never marked as proxy-verified premium players.
*/
final class DisabledPremiumVerificationManager implements PremiumVerificationManager {

@Override
public void register() {
}

@Override
public void refreshRegistration() {
}

@Override
public UUID getVerifiedPremiumUuid(String normalizedName) {
return null;
}

@Override
public void clearVerifiedPremium(String normalizedName) {
}

@Override
public void shutdown() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fr.xephi.authme.bungee.premium;

import java.util.UUID;

/**
* Proxy-side premium verification. Implementations that use PacketEvents are only instantiated
* (and therefore only loaded) when the PacketEvents plugin is installed on the proxy.
*/
public interface PremiumVerificationManager {

/**
* Registers the premium verification listeners.
*/
void register();

/**
* Re-evaluates whether the premium verification listeners should be active.
*/
void refreshRegistration();

/**
* @param normalizedName lower-case player name
* @return the verified premium UUID, or {@code null} if the player was not verified
*/
UUID getVerifiedPremiumUuid(String normalizedName);

/**
* Discards the verification result of the given player.
*
* @param normalizedName lower-case player name
*/
void clearVerifiedPremium(String normalizedName);

/**
* Unregisters all listeners and releases resources.
*/
void shutdown();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fr.xephi.authme.bungee.premium;

import net.md_5.bungee.api.ProxyServer;

import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Logger;

/**
* Creates the {@link PremiumVerificationManager} for the Bungee proxy. The PacketEvents-backed
* implementation is only instantiated when PacketEvents is installed, so that classes referencing
* the PacketEvents library are never loaded on proxies without it.
*/
public final class PremiumVerificationManagers {

private static final String PACKET_EVENTS_PLUGIN_NAME = "packetevents";

private PremiumVerificationManagers() {
}

public static PremiumVerificationManager create(ProxyServer proxyServer, Logger logger,
Predicate<String> requiresVerification,
Predicate<String> isPendingVerification,
Consumer<String> pendingVerificationFailureHandler,
BooleanSupplier keepOfflineUuidCompatibility) {
if (proxyServer.getPluginManager().getPlugin(PACKET_EVENTS_PLUGIN_NAME) == null) {
logger.warning("PacketEvents is not installed on the proxy; premium proxy verification stays disabled");
return new DisabledPremiumVerificationManager();
}
try {
return new BungeePremiumVerificationManager(proxyServer, logger, requiresVerification,
isPendingVerification, pendingVerificationFailureHandler, keepOfflineUuidCompatibility);
} catch (LinkageError e) {
logger.warning("PacketEvents is installed but its classes could not be loaded; premium proxy "
+ "verification stays disabled: " + e);
return new DisabledPremiumVerificationManager();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
Expand Down Expand Up @@ -80,14 +83,26 @@ class BungeeProxyBridgeTest {
private ServerConnectEvent serverConnectEvent;

@Mock
private PlayerHandshakeEvent playerHandshakeEvent;
private PreLoginEvent preLoginEvent;

@Mock
private PendingConnection pendingConnection;

@Mock
private PluginManager pluginManager;

@Mock
private Plugin packetEventsPlugin;

@Captor
private ArgumentCaptor<byte[]> payloadCaptor;

@BeforeEach
void registerPacketEventsOnTheProxy() {
given(proxyServer.getPluginManager()).willReturn(pluginManager);
given(pluginManager.getPlugin("packetevents")).willReturn(packetEventsPlugin);
}

@Test
void shouldTrackAuthenticatedPlayerAndForwardPerformLoginOnServerSwitch() {
given(pluginMessageEvent.isCancelled()).willReturn(false);
Expand Down Expand Up @@ -452,13 +467,30 @@ void shouldForceOnlineModeForPremiumHandshakeWhenOfflineCompatibilityDisabled()
given(pluginMessageEvent.getTag()).willReturn(BungeeProxyBridge.AUTHME_CHANNEL);
given(pluginMessageEvent.getSender()).willReturn(sourceServer);
given(pluginMessageEvent.getData()).willReturn(createAuthMePayload("premium.set", "Alice"));
given(playerHandshakeEvent.getConnection()).willReturn(pendingConnection);
given(preLoginEvent.getConnection()).willReturn(pendingConnection);
given(pendingConnection.getName()).willReturn("Alice");
given(pendingConnection.isOnlineMode()).willReturn(false);

BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onPreLogin(preLoginEvent);

verify(pendingConnection).setOnlineMode(true);
}

@Test
void shouldForceOnlineModeForPendingPremiumEnrollmentAtPreLogin() {
given(pluginMessageEvent.isCancelled()).willReturn(false);
given(pluginMessageEvent.getTag()).willReturn(BungeeProxyBridge.AUTHME_CHANNEL);
given(pluginMessageEvent.getSender()).willReturn(sourceServer);
given(pluginMessageEvent.getData()).willReturn(createAuthMePayload("premium.pending.set", "Alice"));
given(preLoginEvent.getConnection()).willReturn(pendingConnection);
given(pendingConnection.getName()).willReturn("Alice");
given(pendingConnection.isOnlineMode()).willReturn(false);

BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onPlayerHandshake(playerHandshakeEvent);
bridge.onPreLogin(preLoginEvent);

verify(pendingConnection).setOnlineMode(true);
}
Expand All @@ -469,13 +501,13 @@ void shouldForceOnlineModeForPremiumHandshakeAfterChunkedPremiumListResync() {
given(pluginMessageEvent.getTag()).willReturn(BungeeProxyBridge.AUTHME_CHANNEL);
given(pluginMessageEvent.getSender()).willReturn(sourceServer);
given(pluginMessageEvent.getData()).willReturn(createChunkPayload(0, true, "Alice"));
given(playerHandshakeEvent.getConnection()).willReturn(pendingConnection);
given(preLoginEvent.getConnection()).willReturn(pendingConnection);
given(pendingConnection.getName()).willReturn("Alice");
given(pendingConnection.isOnlineMode()).willReturn(false);

BungeeProxyBridge bridge = new BungeeProxyBridge(proxyServer, logger, createConfiguration(), new BungeeAuthenticationStore(), null);
bridge.onPluginMessage(pluginMessageEvent);
bridge.onPlayerHandshake(playerHandshakeEvent);
bridge.onPreLogin(preLoginEvent);

verify(pendingConnection).setOnlineMode(true);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fr.xephi.authme.bungee.premium;

import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.PluginManager;
import org.junit.jupiter.api.Test;

import java.util.logging.Logger;

import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

class PremiumVerificationManagersTest {

@Test
void shouldUseDisabledManagerWhenPacketEventsIsNotInstalled() {
ProxyServer proxyServer = mock(ProxyServer.class);
given(proxyServer.getPluginManager()).willReturn(mock(PluginManager.class));

PremiumVerificationManager manager = PremiumVerificationManagers.create(
proxyServer, mock(Logger.class), name -> true, name -> false, name -> { }, () -> true);

assertNull(manager.getVerifiedPremiumUuid("alice"));
manager.register();
manager.refreshRegistration();
manager.clearVerifiedPremium("alice");
manager.shutdown();
}
}
Loading
Loading