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

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