|
| 1 | +package pl.syntaxdevteam.authgatewayx.paper.client |
| 2 | + |
| 3 | +import io.papermc.paper.threadedregions.scheduler.EntityScheduler |
| 4 | +import io.papermc.paper.threadedregions.scheduler.ScheduledTask |
| 5 | +import org.bukkit.entity.Player |
| 6 | +import org.bukkit.Server |
| 7 | +import org.bukkit.event.player.PlayerQuitEvent |
| 8 | +import org.bukkit.plugin.Plugin |
| 9 | +import org.bukkit.plugin.messaging.Messenger |
| 10 | +import pl.syntaxdevteam.authgatewayx.api.AuthenticationStatusProvider |
| 11 | +import pl.syntaxdevteam.authgatewayx.security.client.ClientAuthenticationProtocol |
| 12 | +import java.lang.reflect.Proxy |
| 13 | +import java.util.UUID |
| 14 | +import java.util.function.Consumer |
| 15 | +import kotlin.test.Test |
| 16 | +import kotlin.test.assertEquals |
| 17 | +import kotlin.test.assertTrue |
| 18 | + |
| 19 | +class PaperClientAuthenticationChannelTest { |
| 20 | + private fun <T> mock(type: Class<T>, handler: (String, Array<out Any?>?) -> Any?): T = type.cast( |
| 21 | + Proxy.newProxyInstance(type.classLoader, arrayOf(type)) { self, method, args -> |
| 22 | + when (method.name) { |
| 23 | + "hashCode" -> System.identityHashCode(self) |
| 24 | + "equals" -> self === args?.get(0) |
| 25 | + "toString" -> type.simpleName |
| 26 | + else -> handler(method.name, args) |
| 27 | + } |
| 28 | + }) |
| 29 | + |
| 30 | + private inner class Fixture { |
| 31 | + var authenticated = false |
| 32 | + var now = 0L |
| 33 | + var scheduled = 0 |
| 34 | + var cancelled = 0 |
| 35 | + var tick: Consumer<ScheduledTask>? = null |
| 36 | + val replies = mutableListOf<ByteArray>() |
| 37 | + val task = mock(ScheduledTask::class.java) { name, _ -> |
| 38 | + if (name == "cancel") { cancelled++; null } else null |
| 39 | + } |
| 40 | + val scheduler = mock(EntityScheduler::class.java) { name, args -> |
| 41 | + if (name == "runAtFixedRate") { |
| 42 | + scheduled++ |
| 43 | + @Suppress("UNCHECKED_CAST") |
| 44 | + tick = args!![1] as Consumer<ScheduledTask> |
| 45 | + task |
| 46 | + } else null |
| 47 | + } |
| 48 | + val messenger = mock(Messenger::class.java) { _, _ -> null } |
| 49 | + val server = mock(Server::class.java) { name, _ -> if (name == "getMessenger") messenger else null } |
| 50 | + val plugin = mock(Plugin::class.java) { name, _ -> if (name == "getServer") server else null } |
| 51 | + val player = mock(Player::class.java) { name, args -> when (name) { |
| 52 | + "getUniqueId" -> UUID(0, 1) |
| 53 | + "getScheduler" -> scheduler |
| 54 | + "isOnline" -> true |
| 55 | + "sendPluginMessage" -> { replies += (args!![2] as ByteArray).copyOf(); null } |
| 56 | + else -> null |
| 57 | + } } |
| 58 | + val channel = PaperClientAuthenticationChannel(plugin, AuthenticationStatusProvider { authenticated }, { now }) |
| 59 | + val nonce = UUID.randomUUID().toString() |
| 60 | + fun subscribe() = channel.onPluginMessageReceived(ClientAuthenticationProtocol.CHANNEL, player, |
| 61 | + ClientAuthenticationProtocol.subscription(nonce)) |
| 62 | + } |
| 63 | + |
| 64 | + @Test fun `waits for ACTIVE and repeated subscriptions do not create additional tasks`() { |
| 65 | + val f = Fixture() |
| 66 | + f.subscribe(); f.subscribe() |
| 67 | + assertEquals(1, f.scheduled) |
| 68 | + f.tick!!.accept(f.task) |
| 69 | + assertTrue(f.replies.isEmpty()) |
| 70 | + f.authenticated = true |
| 71 | + f.tick!!.accept(f.task); f.tick!!.accept(f.task) |
| 72 | + assertEquals(1, f.replies.size) |
| 73 | + assertTrue(ClientAuthenticationProtocol.confirms(f.replies.single(), f.nonce)) |
| 74 | + } |
| 75 | + |
| 76 | + @Test fun `client proof is not subscription and disconnect blocks stale callbacks`() { |
| 77 | + val f = Fixture() |
| 78 | + f.channel.onPluginMessageReceived(ClientAuthenticationProtocol.CHANNEL, f.player, |
| 79 | + ClientAuthenticationProtocol.authenticated(f.nonce)) |
| 80 | + assertEquals(0, f.scheduled) |
| 81 | + f.subscribe() |
| 82 | + f.channel.onQuit(PlayerQuitEvent(f.player, null as net.kyori.adventure.text.Component?, PlayerQuitEvent.QuitReason.DISCONNECTED)) |
| 83 | + f.authenticated = true |
| 84 | + f.tick!!.accept(f.task) |
| 85 | + assertTrue(f.replies.isEmpty()) |
| 86 | + } |
| 87 | + |
| 88 | + @Test fun `expired subscription and shutdown never send late confirmations`() { |
| 89 | + val f = Fixture(); f.subscribe(); f.now = 300_000; f.authenticated = true |
| 90 | + f.tick!!.accept(f.task) |
| 91 | + assertTrue(f.replies.isEmpty()) |
| 92 | + f.subscribe(); f.channel.close(); f.tick!!.accept(f.task) |
| 93 | + assertTrue(f.replies.isEmpty()) |
| 94 | + } |
| 95 | +} |
0 commit comments