diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java index 162d3303a..0a2fd7fdf 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java @@ -1582,15 +1582,15 @@ protected void handleLoginMessage(JsonEnvelope message) { boolean accepted = false; String deliveryServer = server; if (legacy) { - // Preserve the original login contract for cached rewards. In - // PLUGINMESSAGING mode the player-facing proxy is authoritative for online - // state and backend presence messages are disabled entirely. - accepted = true; if (method == BungeeMethod.PLUGINMESSAGING) { String proxyServer = getCurrentPlayerServer(player); - if (proxyServer != null && !proxyServer.isBlank()) { + accepted = isLegacyLoginDestinationAuthoritative(player, uuid, proxyServer); + if (accepted) { deliveryServer = proxyServer; } + } else if (method != null && method.supportsBackendPresence() + && isPresenceServerValid(server, VotingPluginWire.SUB_LOGIN)) { + accepted = isLegacyLoginDestinationAuthoritative(player, uuid, server); } } else if (method != null && method.supportsBackendPresence() && event.connectionId != null && isPresenceServerValid(server, VotingPluginWire.SUB_LOGIN) @@ -1618,6 +1618,52 @@ && isPresenceGenerationValid(event.backendIncarnationId, event.backendStartedAt, } } + /** + * Validates a legacy login against an authority independent of the envelope. + * Player-facing proxies use their native live route and UUID. A dedicated + * voting proxy has no native player session, so it requires an exact modern + * presence match for the claimed destination. + */ + private boolean isLegacyLoginDestinationAuthoritative(String player, String uuid, String server) { + if (server == null || server.isBlank()) { + return false; + } + + UUID claimedUuid; + try { + claimedUuid = UUID.fromString(uuid.trim()); + } catch (RuntimeException e) { + return false; + } + + if (isDedicatedVotingProxyEnabled()) { + PlayerPresence presence = backendPlayerPresenceTracker.getPlayer(player).orElse(null); + return presence != null && presence.getServer().equalsIgnoreCase(server) + && (!getConfig().getOnlineMode() || presence.getUuid().equals(claimedUuid)); + } + + if (!isPlayerOnline(player)) { + return false; + } + String proxyServer = getCurrentPlayerServer(player); + if (proxyServer == null || !proxyServer.equalsIgnoreCase(server)) { + return false; + } + if (!getConfig().getOnlineMode()) { + return true; + } + + String authoritativeUuid = getUUID(player); + if (authoritativeUuid == null || authoritativeUuid.isBlank()) { + return false; + } + try { + return claimedUuid.equals(UUID.fromString(authoritativeUuid.trim())); + } catch (IllegalArgumentException e) { + return false; + } + } + private VoteLogMysqlTable voteLogMysqlTable; @Getter diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java index 3c03997bf..f507d910e 100644 --- a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java @@ -125,10 +125,12 @@ void immediatePluginMessageReportsActualDeliveryResult() { } @Test - void pluginMessagingLegacyLoginUsesTheProxyCurrentServer() { + void pluginMessagingLegacyLoginUsesTheProxyCurrentServerAndUuid() { String uuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); votingPluginProxy.setMethod(BungeeMethod.PLUGINMESSAGING); VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(uuid).when(spyProxy).getUUID("Player"); doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "claimed-backend")); @@ -137,15 +139,136 @@ void pluginMessagingLegacyLoginUsesTheProxyCurrentServer() { } @Test - void standaloneTransportLegacyLoginKeepsOriginalCompatibilityPath() { + void pluginMessagingLegacyLoginRejectsMismatchedUuid() { + String authoritativeUuid = java.util.UUID.randomUUID().toString(); + String claimedUuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.PLUGINMESSAGING); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(authoritativeUuid).when(spyProxy).getUUID("Player"); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", claimedUuid, "Server1")); + + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + void standaloneTransportLegacyLoginUsesProxyAuthoritativeRoute() { + String uuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(uuid).when(spyProxy).getUUID("Player"); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "Server1")); + + verify(spyProxy).login("Player", uuid, "Server1"); + } + + @Test + void standaloneTransportLegacyLoginRejectsUnknownServer() { String uuid = java.util.UUID.randomUUID().toString(); votingPluginProxy.setMethod(BungeeMethod.MQTT); VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(false).when(spyProxy).isServerValid("unknown-server"); doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); - spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "survival")); + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "unknown-server")); - verify(spyProxy).login("Player", uuid, "survival"); + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + void standaloneTransportLegacyLoginAcceptsModernToLegacyHandoffUsingProxyRoute() { + java.util.UUID playerUuid = java.util.UUID.randomUUID(); + java.util.UUID incarnation = java.util.UUID.randomUUID(); + long now = System.currentTimeMillis(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + assertTrue(votingPluginProxy.getBackendPlayerPresenceTracker().backendStarted("Server1", incarnation, + 1000L, 1000L, now)); + assertTrue(votingPluginProxy.getBackendPlayerPresenceTracker().playerOnline("Player", + playerUuid.toString(), "Server1", java.util.UUID.randomUUID(), incarnation, 1000L, 1100L, + now)); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn("Server2").when(spyProxy).getCurrentPlayerServer("Player"); + Mockito.doReturn(playerUuid.toString()).when(spyProxy).getUUID("Player"); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest( + VotingPluginWire.login("Player", playerUuid.toString(), "Server2")); + + verify(spyProxy).login("Player", playerUuid.toString(), "Server2"); + } + + @Test + void standaloneTransportLegacyLoginRejectsClaimNotMatchingProxyRoute() { + String uuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(uuid).when(spyProxy).getUUID("Player"); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "Server2")); + + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + void standaloneTransportLegacyLoginRejectsNameUuidMismatch() { + String aliceUuid = java.util.UUID.randomUUID().toString(); + String bobUuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + Mockito.doReturn(aliceUuid).when(spyProxy).getUUID("Alice"); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Alice", bobUuid, "Server1")); + + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + void dedicatedProxyLegacyLoginRequiresConfirmedDestinationPresence() { + java.util.UUID playerUuid = java.util.UUID.randomUUID(); + java.util.UUID incarnation = java.util.UUID.randomUUID(); + long now = System.currentTimeMillis(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + Mockito.when(votingPluginProxy.getConfig().getDedicatedVotingProxy()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + assertTrue(votingPluginProxy.getBackendPlayerPresenceTracker().backendStarted("Server1", incarnation, + 1000L, 1000L, now)); + assertTrue(votingPluginProxy.getBackendPlayerPresenceTracker().playerOnline("Player", + playerUuid.toString(), "Server1", java.util.UUID.randomUUID(), incarnation, 1000L, 1100L, + now)); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest( + VotingPluginWire.login("Player", playerUuid.toString(), "Server2")); + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest( + VotingPluginWire.login("Player", playerUuid.toString(), "Server1")); + verify(spyProxy).login("Player", playerUuid.toString(), "Server1"); + } + + @Test + void dedicatedProxyLegacyLoginRejectsUnknownIdentity() { + String uuid = java.util.UUID.randomUUID().toString(); + Mockito.when(votingPluginProxy.getConfig().getOnlineMode()).thenReturn(true); + Mockito.when(votingPluginProxy.getConfig().getDedicatedVotingProxy()).thenReturn(true); + votingPluginProxy.setMethod(BungeeMethod.MQTT); + VotingPluginProxyTestImpl spyProxy = Mockito.spy(votingPluginProxy); + doNothing().when(spyProxy).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + spyProxy.handleLoginMessageForTest(VotingPluginWire.login("Player", uuid, "Server1")); + + verify(spyProxy, never()).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); } @Test