test_jabberclient.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.words.protocols.jabber.client}
  5. """
  6. from __future__ import absolute_import, division
  7. from hashlib import sha1
  8. from twisted.internet import defer
  9. from twisted.python.compat import unicode
  10. from twisted.trial import unittest
  11. from twisted.words.protocols.jabber import client, error, jid, xmlstream
  12. from twisted.words.protocols.jabber.sasl import SASLInitiatingInitializer
  13. from twisted.words.xish import utility
  14. IQ_AUTH_GET = '/iq[@type="get"]/query[@xmlns="jabber:iq:auth"]'
  15. IQ_AUTH_SET = '/iq[@type="set"]/query[@xmlns="jabber:iq:auth"]'
  16. NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
  17. IQ_BIND_SET = '/iq[@type="set"]/bind[@xmlns="%s"]' % NS_BIND
  18. NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
  19. IQ_SESSION_SET = '/iq[@type="set"]/session[@xmlns="%s"]' % NS_SESSION
  20. class CheckVersionInitializerTests(unittest.TestCase):
  21. def setUp(self):
  22. a = xmlstream.Authenticator()
  23. xs = xmlstream.XmlStream(a)
  24. self.init = client.CheckVersionInitializer(xs)
  25. def testSupported(self):
  26. """
  27. Test supported version number 1.0
  28. """
  29. self.init.xmlstream.version = (1, 0)
  30. self.init.initialize()
  31. def testNotSupported(self):
  32. """
  33. Test unsupported version number 0.0, and check exception.
  34. """
  35. self.init.xmlstream.version = (0, 0)
  36. exc = self.assertRaises(error.StreamError, self.init.initialize)
  37. self.assertEqual('unsupported-version', exc.condition)
  38. class InitiatingInitializerHarness(object):
  39. """
  40. Testing harness for interacting with XML stream initializers.
  41. This sets up an L{utility.XmlPipe} to create a communication channel between
  42. the initializer and the stubbed receiving entity. It features a sink and
  43. source side that both act similarly to a real L{xmlstream.XmlStream}. The
  44. sink is augmented with an authenticator to which initializers can be added.
  45. The harness also provides some utility methods to work with event observers
  46. and deferreds.
  47. """
  48. def setUp(self):
  49. self.output = []
  50. self.pipe = utility.XmlPipe()
  51. self.xmlstream = self.pipe.sink
  52. self.authenticator = xmlstream.ConnectAuthenticator('example.org')
  53. self.xmlstream.authenticator = self.authenticator
  54. def waitFor(self, event, handler):
  55. """
  56. Observe an output event, returning a deferred.
  57. The returned deferred will be fired when the given event has been
  58. observed on the source end of the L{XmlPipe} tied to the protocol
  59. under test. The handler is added as the first callback.
  60. @param event: The event to be observed. See
  61. L{utility.EventDispatcher.addOnetimeObserver}.
  62. @param handler: The handler to be called with the observed event object.
  63. @rtype: L{defer.Deferred}.
  64. """
  65. d = defer.Deferred()
  66. d.addCallback(handler)
  67. self.pipe.source.addOnetimeObserver(event, d.callback)
  68. return d
  69. class IQAuthInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
  70. """
  71. Tests for L{client.IQAuthInitializer}.
  72. """
  73. def setUp(self):
  74. super(IQAuthInitializerTests, self).setUp()
  75. self.init = client.IQAuthInitializer(self.xmlstream)
  76. self.authenticator.jid = jid.JID('user@example.com/resource')
  77. self.authenticator.password = u'secret'
  78. def testPlainText(self):
  79. """
  80. Test plain-text authentication.
  81. Act as a server supporting plain-text authentication and expect the
  82. C{password} field to be filled with the password. Then act as if
  83. authentication succeeds.
  84. """
  85. def onAuthGet(iq):
  86. """
  87. Called when the initializer sent a query for authentication methods.
  88. The response informs the client that plain-text authentication
  89. is supported.
  90. """
  91. # Create server response
  92. response = xmlstream.toResponse(iq, 'result')
  93. response.addElement(('jabber:iq:auth', 'query'))
  94. response.query.addElement('username')
  95. response.query.addElement('password')
  96. response.query.addElement('resource')
  97. # Set up an observer for the next request we expect.
  98. d = self.waitFor(IQ_AUTH_SET, onAuthSet)
  99. # Send server response
  100. self.pipe.source.send(response)
  101. return d
  102. def onAuthSet(iq):
  103. """
  104. Called when the initializer sent the authentication request.
  105. The server checks the credentials and responds with an empty result
  106. signalling success.
  107. """
  108. self.assertEqual('user', unicode(iq.query.username))
  109. self.assertEqual('secret', unicode(iq.query.password))
  110. self.assertEqual('resource', unicode(iq.query.resource))
  111. # Send server response
  112. response = xmlstream.toResponse(iq, 'result')
  113. self.pipe.source.send(response)
  114. # Set up an observer for the request for authentication fields
  115. d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
  116. # Start the initializer
  117. d2 = self.init.initialize()
  118. return defer.gatherResults([d1, d2])
  119. def testDigest(self):
  120. """
  121. Test digest authentication.
  122. Act as a server supporting digest authentication and expect the
  123. C{digest} field to be filled with a sha1 digest of the concatenated
  124. stream session identifier and password. Then act as if authentication
  125. succeeds.
  126. """
  127. def onAuthGet(iq):
  128. """
  129. Called when the initializer sent a query for authentication methods.
  130. The response informs the client that digest authentication is
  131. supported.
  132. """
  133. # Create server response
  134. response = xmlstream.toResponse(iq, 'result')
  135. response.addElement(('jabber:iq:auth', 'query'))
  136. response.query.addElement('username')
  137. response.query.addElement('digest')
  138. response.query.addElement('resource')
  139. # Set up an observer for the next request we expect.
  140. d = self.waitFor(IQ_AUTH_SET, onAuthSet)
  141. # Send server response
  142. self.pipe.source.send(response)
  143. return d
  144. def onAuthSet(iq):
  145. """
  146. Called when the initializer sent the authentication request.
  147. The server checks the credentials and responds with an empty result
  148. signalling success.
  149. """
  150. self.assertEqual('user', unicode(iq.query.username))
  151. self.assertEqual(sha1(b'12345secret').hexdigest(),
  152. unicode(iq.query.digest))
  153. self.assertEqual('resource', unicode(iq.query.resource))
  154. # Send server response
  155. response = xmlstream.toResponse(iq, 'result')
  156. self.pipe.source.send(response)
  157. # Digest authentication relies on the stream session identifier. Set it.
  158. self.xmlstream.sid = u'12345'
  159. # Set up an observer for the request for authentication fields
  160. d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
  161. # Start the initializer
  162. d2 = self.init.initialize()
  163. return defer.gatherResults([d1, d2])
  164. def testFailRequestFields(self):
  165. """
  166. Test initializer failure of request for fields for authentication.
  167. """
  168. def onAuthGet(iq):
  169. """
  170. Called when the initializer sent a query for authentication methods.
  171. The server responds that the client is not authorized to authenticate.
  172. """
  173. response = error.StanzaError('not-authorized').toResponse(iq)
  174. self.pipe.source.send(response)
  175. # Set up an observer for the request for authentication fields
  176. d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
  177. # Start the initializer
  178. d2 = self.init.initialize()
  179. # The initialized should fail with a stanza error.
  180. self.assertFailure(d2, error.StanzaError)
  181. return defer.gatherResults([d1, d2])
  182. def testFailAuth(self):
  183. """
  184. Test initializer failure to authenticate.
  185. """
  186. def onAuthGet(iq):
  187. """
  188. Called when the initializer sent a query for authentication methods.
  189. The response informs the client that plain-text authentication
  190. is supported.
  191. """
  192. # Send server response
  193. response = xmlstream.toResponse(iq, 'result')
  194. response.addElement(('jabber:iq:auth', 'query'))
  195. response.query.addElement('username')
  196. response.query.addElement('password')
  197. response.query.addElement('resource')
  198. # Set up an observer for the next request we expect.
  199. d = self.waitFor(IQ_AUTH_SET, onAuthSet)
  200. # Send server response
  201. self.pipe.source.send(response)
  202. return d
  203. def onAuthSet(iq):
  204. """
  205. Called when the initializer sent the authentication request.
  206. The server checks the credentials and responds with a not-authorized
  207. stanza error.
  208. """
  209. response = error.StanzaError('not-authorized').toResponse(iq)
  210. self.pipe.source.send(response)
  211. # Set up an observer for the request for authentication fields
  212. d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
  213. # Start the initializer
  214. d2 = self.init.initialize()
  215. # The initializer should fail with a stanza error.
  216. self.assertFailure(d2, error.StanzaError)
  217. return defer.gatherResults([d1, d2])
  218. class BindInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
  219. """
  220. Tests for L{client.BindInitializer}.
  221. """
  222. def setUp(self):
  223. super(BindInitializerTests, self).setUp()
  224. self.init = client.BindInitializer(self.xmlstream)
  225. self.authenticator.jid = jid.JID('user@example.com/resource')
  226. def testBasic(self):
  227. """
  228. Set up a stream, and act as if resource binding succeeds.
  229. """
  230. def onBind(iq):
  231. response = xmlstream.toResponse(iq, 'result')
  232. response.addElement((NS_BIND, 'bind'))
  233. response.bind.addElement('jid',
  234. content=u'user@example.com/other resource')
  235. self.pipe.source.send(response)
  236. def cb(result):
  237. self.assertEqual(jid.JID('user@example.com/other resource'),
  238. self.authenticator.jid)
  239. d1 = self.waitFor(IQ_BIND_SET, onBind)
  240. d2 = self.init.start()
  241. d2.addCallback(cb)
  242. return defer.gatherResults([d1, d2])
  243. def testFailure(self):
  244. """
  245. Set up a stream, and act as if resource binding fails.
  246. """
  247. def onBind(iq):
  248. response = error.StanzaError('conflict').toResponse(iq)
  249. self.pipe.source.send(response)
  250. d1 = self.waitFor(IQ_BIND_SET, onBind)
  251. d2 = self.init.start()
  252. self.assertFailure(d2, error.StanzaError)
  253. return defer.gatherResults([d1, d2])
  254. class SessionInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
  255. """
  256. Tests for L{client.SessionInitializer}.
  257. """
  258. def setUp(self):
  259. super(SessionInitializerTests, self).setUp()
  260. self.init = client.SessionInitializer(self.xmlstream)
  261. def testSuccess(self):
  262. """
  263. Set up a stream, and act as if session establishment succeeds.
  264. """
  265. def onSession(iq):
  266. response = xmlstream.toResponse(iq, 'result')
  267. self.pipe.source.send(response)
  268. d1 = self.waitFor(IQ_SESSION_SET, onSession)
  269. d2 = self.init.start()
  270. return defer.gatherResults([d1, d2])
  271. def testFailure(self):
  272. """
  273. Set up a stream, and act as if session establishment fails.
  274. """
  275. def onSession(iq):
  276. response = error.StanzaError('forbidden').toResponse(iq)
  277. self.pipe.source.send(response)
  278. d1 = self.waitFor(IQ_SESSION_SET, onSession)
  279. d2 = self.init.start()
  280. self.assertFailure(d2, error.StanzaError)
  281. return defer.gatherResults([d1, d2])
  282. class XMPPAuthenticatorTests(unittest.TestCase):
  283. """
  284. Test for both XMPPAuthenticator and XMPPClientFactory.
  285. """
  286. def testBasic(self):
  287. """
  288. Test basic operations.
  289. Setup an XMPPClientFactory, which sets up an XMPPAuthenticator, and let
  290. it produce a protocol instance. Then inspect the instance variables of
  291. the authenticator and XML stream objects.
  292. """
  293. self.client_jid = jid.JID('user@example.com/resource')
  294. # Get an XmlStream instance. Note that it gets initialized with the
  295. # XMPPAuthenticator (that has its associateWithXmlStream called) that
  296. # is in turn initialized with the arguments to the factory.
  297. xs = client.XMPPClientFactory(self.client_jid,
  298. 'secret').buildProtocol(None)
  299. # test authenticator's instance variables
  300. self.assertEqual('example.com', xs.authenticator.otherHost)
  301. self.assertEqual(self.client_jid, xs.authenticator.jid)
  302. self.assertEqual('secret', xs.authenticator.password)
  303. # test list of initializers
  304. version, tls, sasl, bind, session = xs.initializers
  305. self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
  306. self.assertIsInstance(sasl, SASLInitiatingInitializer)
  307. self.assertIsInstance(bind, client.BindInitializer)
  308. self.assertIsInstance(session, client.SessionInitializer)
  309. self.assertFalse(tls.required)
  310. self.assertTrue(sasl.required)
  311. self.assertFalse(bind.required)
  312. self.assertFalse(session.required)