You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CapabilitiesHandlerTest.kt 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. package com.dmdirc.ktirc.events
  2. import com.dmdirc.ktirc.IrcClient
  3. import com.dmdirc.ktirc.TestConstants
  4. import com.dmdirc.ktirc.model.CapabilitiesNegotiationState
  5. import com.dmdirc.ktirc.model.Capability
  6. import com.dmdirc.ktirc.model.ServerState
  7. import com.dmdirc.ktirc.sasl.SaslMechanism
  8. import com.dmdirc.ktirc.sasl.fromBase64
  9. import com.dmdirc.ktirc.sasl.toBase64
  10. import com.nhaarman.mockitokotlin2.*
  11. import org.junit.jupiter.api.Assertions.*
  12. import org.junit.jupiter.api.Test
  13. internal class CapabilitiesHandlerTest {
  14. private val saslMech1 = mock<SaslMechanism> {
  15. on { priority } doReturn 1
  16. on { ircName } doReturn "mech1"
  17. }
  18. private val saslMech2 = mock<SaslMechanism> {
  19. on { priority } doReturn 2
  20. on { ircName } doReturn "mech2"
  21. }
  22. private val saslMech3 = mock<SaslMechanism> {
  23. on { priority } doReturn 3
  24. on { ircName } doReturn "mech3"
  25. }
  26. private val handler = CapabilitiesHandler()
  27. private val serverState = ServerState("", "", null)
  28. private val ircClient = mock<IrcClient> {
  29. on { serverState } doReturn serverState
  30. }
  31. @Test
  32. fun `adds new capabilities to the state`() {
  33. handler.processEvent(ircClient, ServerCapabilitiesReceived(TestConstants.time, hashMapOf(
  34. Capability.EchoMessages to "",
  35. Capability.HostsInNamesReply to "123"
  36. )))
  37. assertEquals(2, serverState.capabilities.advertisedCapabilities.size)
  38. assertEquals("", serverState.capabilities.advertisedCapabilities[Capability.EchoMessages])
  39. assertEquals("123", serverState.capabilities.advertisedCapabilities[Capability.HostsInNamesReply])
  40. }
  41. @Test
  42. fun `updates negotiation state when capabilities finished`() {
  43. serverState.capabilities.advertisedCapabilities[Capability.EchoMessages] = ""
  44. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  45. assertEquals(CapabilitiesNegotiationState.AWAITING_ACK, serverState.capabilities.negotiationState)
  46. }
  47. @Test
  48. fun `sends REQ when capabilities received`() {
  49. serverState.capabilities.advertisedCapabilities[Capability.EchoMessages] = ""
  50. serverState.capabilities.advertisedCapabilities[Capability.AccountChangeMessages] = ""
  51. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  52. verify(ircClient).send(argThat { equals("CAP REQ :echo-message account-notify") || equals("CAP REQ :account-notify echo-message") })
  53. }
  54. @Test
  55. fun `sends END when blank capabilities received`() {
  56. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  57. verify(ircClient).send("CAP END")
  58. }
  59. @Test
  60. fun `updates negotiation when blank capabilities received`() {
  61. handler.processEvent(ircClient, ServerCapabilitiesFinished(TestConstants.time))
  62. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  63. }
  64. @Test
  65. fun `sends END when capabilities acknowledged and no enabled mechanisms`() {
  66. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  67. Capability.EchoMessages to "",
  68. Capability.HostsInNamesReply to ""
  69. )))
  70. verify(ircClient).send("CAP END")
  71. }
  72. @Test
  73. fun `sends END when capabilities acknowledged and no sasl state`() {
  74. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  75. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  76. Capability.EchoMessages to "",
  77. Capability.HostsInNamesReply to ""
  78. )))
  79. verify(ircClient).send("CAP END")
  80. }
  81. @Test
  82. fun `sends END when capabilities acknowledged and no shared mechanism`() {
  83. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  84. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "fake1,fake2"
  85. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  86. Capability.SaslAuthentication to "",
  87. Capability.HostsInNamesReply to ""
  88. )))
  89. verify(ircClient).send("CAP END")
  90. }
  91. @Test
  92. fun `sets current SASL mechanism when capabilities acknowledged with shared mechanism`() {
  93. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  94. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "mech1,fake2"
  95. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  96. Capability.SaslAuthentication to "",
  97. Capability.HostsInNamesReply to ""
  98. )))
  99. assertSame(saslMech1, serverState.sasl.currentMechanism)
  100. }
  101. @Test
  102. fun `sets current SASL mechanism when capabilities acknowledged with no declared mechanisms`() {
  103. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  104. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = ""
  105. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  106. Capability.SaslAuthentication to "",
  107. Capability.HostsInNamesReply to ""
  108. )))
  109. assertSame(saslMech3, serverState.sasl.currentMechanism)
  110. }
  111. @Test
  112. fun `sends authenticate when capabilities acknowledged with shared mechanism`() {
  113. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  114. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "mech1,fake2"
  115. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  116. Capability.SaslAuthentication to "",
  117. Capability.HostsInNamesReply to ""
  118. )))
  119. verify(ircClient).send("AUTHENTICATE mech1")
  120. }
  121. @Test
  122. fun `sends authenticate when capabilities acknowledged with no declared mechanisms`() {
  123. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  124. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = ""
  125. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  126. Capability.SaslAuthentication to "",
  127. Capability.HostsInNamesReply to ""
  128. )))
  129. verify(ircClient).send("AUTHENTICATE mech3")
  130. }
  131. @Test
  132. fun `updates negotiation state when capabilities acknowledged with shared mechanism`() {
  133. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  134. serverState.capabilities.advertisedCapabilities[Capability.SaslAuthentication] = "mech1,fake2"
  135. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  136. Capability.SaslAuthentication to "",
  137. Capability.HostsInNamesReply to ""
  138. )))
  139. assertEquals(CapabilitiesNegotiationState.AUTHENTICATING, serverState.capabilities.negotiationState)
  140. }
  141. @Test
  142. fun `updates negotiation state when capabilities acknowledged`() {
  143. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  144. Capability.EchoMessages to "",
  145. Capability.HostsInNamesReply to "123"
  146. )))
  147. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  148. }
  149. @Test
  150. fun `stores enabled caps when capabilities acknowledged`() {
  151. handler.processEvent(ircClient, ServerCapabilitiesAcknowledged(TestConstants.time, hashMapOf(
  152. Capability.EchoMessages to "",
  153. Capability.HostsInNamesReply to "123"
  154. )))
  155. assertEquals(2, serverState.capabilities.enabledCapabilities.size)
  156. assertEquals("", serverState.capabilities.enabledCapabilities[Capability.EchoMessages])
  157. assertEquals("123", serverState.capabilities.enabledCapabilities[Capability.HostsInNamesReply])
  158. }
  159. @Test
  160. fun `aborts authentication attempt if not expecting one`() {
  161. serverState.sasl.currentMechanism = null
  162. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "+"))
  163. verify(ircClient).send("AUTHENTICATE *")
  164. }
  165. @Test
  166. fun `passes authentication message to mechanism if in auth process`() {
  167. serverState.sasl.currentMechanism = saslMech1
  168. val argument = "ABC"
  169. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  170. verify(saslMech1).handleAuthenticationEvent(ircClient, argument.fromBase64())
  171. }
  172. @Test
  173. fun `stores partial authentication message if it's 400 bytes long`() {
  174. serverState.sasl.currentMechanism = saslMech1
  175. val argument = "A".repeat(400)
  176. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  177. assertEquals(argument, serverState.sasl.saslBuffer)
  178. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  179. }
  180. @Test
  181. fun `appends authentication messages if it's 400 bytes long and data already exists`() {
  182. serverState.sasl.currentMechanism = saslMech1
  183. serverState.sasl.saslBuffer = "A".repeat(400)
  184. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, "B".repeat(400)))
  185. assertEquals("A".repeat(400) + "B".repeat(400), serverState.sasl.saslBuffer)
  186. verify(saslMech1, never()).handleAuthenticationEvent(any(), any())
  187. }
  188. @Test
  189. fun `reconstructs partial authentication message to mechanism if data stored and partial received`() {
  190. serverState.sasl.currentMechanism = saslMech1
  191. serverState.sasl.saslBuffer = "A".repeat(400)
  192. val argument = "ABCD"
  193. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, argument))
  194. val captor = argumentCaptor<ByteArray>()
  195. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  196. assertEquals("A".repeat(400) + "ABCD", captor.firstValue.toBase64())
  197. }
  198. @Test
  199. fun `reconstructs partial authentication message to mechanism if data stored and null received`() {
  200. serverState.sasl.currentMechanism = saslMech1
  201. serverState.sasl.saslBuffer = "A".repeat(400)
  202. handler.processEvent(ircClient, AuthenticationMessage(TestConstants.time, null))
  203. val captor = argumentCaptor<ByteArray>()
  204. verify(saslMech1).handleAuthenticationEvent(same(ircClient), captor.capture())
  205. assertEquals("A".repeat(400), captor.firstValue.toBase64())
  206. }
  207. @Test
  208. fun `sends END when SASL auth finished`() {
  209. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  210. verify(ircClient).send("CAP END")
  211. }
  212. @Test
  213. fun `sets negotiation state when SASL auth finished`() {
  214. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  215. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  216. }
  217. @Test
  218. fun `resets SASL state when SASL auth finished`() {
  219. with (serverState.sasl) {
  220. currentMechanism = saslMech1
  221. saslBuffer = "HackThePlanet"
  222. mechanismState = "root@thegibson"
  223. }
  224. handler.processEvent(ircClient, SaslFinished(TestConstants.time, true))
  225. with (serverState.sasl) {
  226. assertNull(currentMechanism)
  227. assertEquals("", saslBuffer)
  228. assertNull(mechanismState)
  229. }
  230. }
  231. @Test
  232. fun `sends a new authenticate request when sasl mechanism rejected and new one is acceptable`() {
  233. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  234. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("mech1", "fake2")))
  235. verify(ircClient).send("AUTHENTICATE mech1")
  236. }
  237. @Test
  238. fun `sends cap end when sasl mechanism rejected and no new one is acceptable`() {
  239. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  240. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("fake1", "fake2")))
  241. verify(ircClient).send("CAP END")
  242. }
  243. @Test
  244. fun `sets negotiation state when sasl mechanism rejected and no new one is acceptable`() {
  245. serverState.sasl.mechanisms.addAll(listOf(saslMech1, saslMech2, saslMech3))
  246. handler.processEvent(ircClient, SaslMechanismNotAvailableError(TestConstants.time, listOf("fake1", "fake2")))
  247. assertEquals(CapabilitiesNegotiationState.FINISHED, serverState.capabilities.negotiationState)
  248. }
  249. }