source:
ralphm-patches/c2s_stanza_handlers.patch
Last change on this file was 82:276bc45eb40b, checked in by Ralph Meijer <ralphm@…>, 6 years ago | |
---|---|
File size: 22.5 KB |
-
wokkel/client.py
# HG changeset patch # Parent 70ce9e97aa58780ac7f75a177fe8c0d2aaf1bfc6 # Parent 92b3f7089a61a8998999b67562917675732f8a4e Add c2s protocol handlers for iq, message and presence stanzas. TODO: * Add tests. * Add docstrings. * Save last unavailable presence for future probes. diff --git a/wokkel/client.py b/wokkel/client.py
a b 20 20 from twisted.words.protocols.jabber import client, error, sasl, xmlstream 21 21 from twisted.words.xish import domish 22 22 23 from wokkel import generic 23 from wokkel import generic, xmppim 24 24 from wokkel.iwokkel import IUserSession 25 25 from wokkel.subprotocols import ServerStreamManager 26 26 from wokkel.subprotocols import StreamManager … … 548 548 return [ 549 549 generic.StanzaForwarder(), 550 550 RecipientAddressStamper(), 551 xmppim.UserSessionPresenceProtocol(), 552 xmppim.UserRosterProtocol(), 551 553 ] -
wokkel/test/test_xmppim.py
diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py
a b 5 5 Tests for L{wokkel.xmppim}. 6 6 """ 7 7 8 from zope.interface import verify8 from zope.interface import implementer, verify 9 9 10 10 from twisted.cred import checkers, error as ecred 11 11 from twisted.cred.portal import IRealm … … 17 17 from twisted.words.xish import domish, utility 18 18 19 19 from wokkel import ewokkel, component, xmppim 20 from wokkel.generic import ErrorStanza, parseXml20 from wokkel.generic import ErrorStanza, Stanza, parseXml 21 21 from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub 22 22 from wokkel.subprotocols import IQHandlerMixin 23 23 … … 2031 2031 d = self.sessionManager.probePresence(user) 2032 2032 d.addCallback(cb) 2033 2033 return d 2034 2035 2036 2037 class AccountIQHandlerTest(unittest.TestCase): 2038 """ 2039 Tests for L{xmppim.AccountIQHandler}. 2040 """ 2041 2042 def setUp(self): 2043 self.user = xmppim.User(JID(u'user@example.org')) 2044 users = {self.user.entity: self.user} 2045 realm = xmppim.StaticRealm(u'example.org', users) 2046 2047 self.stub = XmlStreamStub() 2048 self.protocol = xmppim.AccountIQHandler(realm) 2049 self.protocol.makeConnection(self.stub.xmlstream) 2050 self.protocol.connectionInitialized() 2051 2052 2053 def test_onIQ(self): 2054 """ 2055 IQ stanzas are observed. 2056 """ 2057 received = [] 2058 self.patch(self.protocol, 'iqReceived', received.append) 2059 2060 xml = """ 2061 <iq to='user@example.org/Home' from='contact@example.com/Work'> 2062 <query xmlns='jabber:iq:version'/> 2063 </iq> 2064 """ 2065 2066 iq = parseXml(xml) 2067 self.stub.send(iq) 2068 2069 self.assertEqual(1, len(received)) 2070 stanza = received[-1] 2071 self.assertEqual(JID(u'user@example.org/Home'), stanza.recipient) 2072 2073 2074 def test_onIQNotUser(self): 2075 """ 2076 IQs to JIDs without local part are ignored. 2077 """ 2078 xml = """ 2079 <iq to='example.org' from='contact@example.com/Work'> 2080 <query xmlns='jabber:iq:version'/> 2081 </iq> 2082 """ 2083 2084 iq = parseXml(xml) 2085 self.stub.send(iq) 2086 2087 self.assertFalse(getattr(iq, 'handled')) 2088 2089 2090 def test_onIQNoResource(self): 2091 """ 2092 IQs to JIDs without resource are ignored. 2093 """ 2094 xml = """ 2095 <iq to='user@example.org' from='contact@example.com/Work'> 2096 <query xmlns='jabber:iq:version'/> 2097 </iq> 2098 """ 2099 2100 iq = parseXml(xml) 2101 self.stub.send(iq) 2102 2103 self.assertFalse(getattr(iq, 'handled')) 2104 2105 2106 def test_iqReceivedDelivered(self): 2107 """ 2108 IQs are delivered to the user's deliverIQ method. 2109 """ 2110 received = [] 2111 self.patch(self.user, 'deliverIQ', received.append) 2112 2113 stanza = Stanza(recipient=JID(u'user@example.org/Home'), 2114 sender=JID(u'contact@example.com/Work')) 2115 stanza.stanzaKind = u'iq' 2116 stanza.stanzaType = u'get' 2117 self.protocol.iqReceived(stanza) 2118 self.assertEqual([stanza], received) 2119 2120 2121 def test_iqReceivedNoSuchUser(self): 2122 """ 2123 IQs are delivered to the user's deliverIQ method. 2124 """ 2125 def deliverIQ(stanza): 2126 raise ewokkel.NoSuchUser() 2127 2128 def cb(error): 2129 self.assertEqual('service-unavailable', error.condition) 2130 2131 self.patch(self.user, 'deliverIQ', deliverIQ) 2132 2133 stanza = Stanza(recipient=JID(u'other@example.org/Home'), 2134 sender=JID(u'contact@example.com/Work')) 2135 stanza.stanzaKind = u'iq' 2136 stanza.stanzaType = u'get' 2137 d = self.protocol.iqReceived(stanza) 2138 self.assertFailure(d, error.StanzaError) 2139 d.addCallback(cb) 2140 return d 2141 2142 2143 2144 class AccountMessageHandlerTest(unittest.TestCase): 2145 """ 2146 Tests for L{xmppim.AccountMessageHandler}. 2147 """ 2148 2149 def setUp(self): 2150 self.user = xmppim.User(JID(u'test@example.org')) 2151 users = {self.user.entity: self.user} 2152 realm = xmppim.StaticRealm(u'example.org', users) 2153 2154 self.stub = XmlStreamStub() 2155 self.protocol = xmppim.AccountMessageHandler(realm) 2156 self.protocol.makeConnection(self.stub.xmlstream) 2157 self.protocol.connectionInitialized() 2158 2159 2160 def test_messageReceived(self): 2161 """ 2162 Message stanzas are observed. 2163 """ 2164 received = [] 2165 self.patch(self.protocol, 'messageReceived', received.append) 2166 2167 xml = """ 2168 <message to='example.org'> 2169 <body>Hello</body> 2170 </message> 2171 """ 2172 2173 message = parseXml(xml) 2174 self.stub.send(message) 2175 2176 self.assertEqual(1, len(received)) 2177 stanza = received[-1] 2178 self.assertEqual(u'Hello', stanza.body) 2179 2180 2181 def test_messageReceivedNotUser(self): 2182 """ 2183 Messages to JIDs without local part are ignored. 2184 2185 This also tests the observer that is set up to handle message stanzas. 2186 """ 2187 xml = """ 2188 <message to='example.org'> 2189 <body>Hello</body> 2190 </message> 2191 """ 2192 2193 message = parseXml(xml) 2194 self.stub.send(message) 2195 2196 self.assertFalse(getattr(message, 'handled')) 2197 2198 2199 def test_messageReceivedDelivered(self): 2200 """ 2201 Messages are delivered to the user's deliverMessage method. 2202 """ 2203 received = [] 2204 self.patch(self.user, 'deliverMessage', received.append) 2205 2206 stanza = xmppim.Message(recipient=JID(u'test@example.org'), 2207 sender=JID(u'other@example.com')) 2208 self.protocol.messageReceived(stanza) 2209 self.assertEqual([stanza], received) 2210 2211 2212 def test_messageReceivedNotImplemented(self): 2213 """ 2214 If NotImplementedError is raised, service-available is returned. 2215 """ 2216 def deliverMessage(stanza): 2217 raise NotImplementedError() 2218 2219 def cb(error): 2220 self.assertEqual('service-unavailable', error.condition) 2221 2222 self.patch(self.user, 'deliverMessage', deliverMessage) 2223 2224 stanza = xmppim.Message(recipient=JID(u'test@example.org'), 2225 sender=JID(u'other@example.com')) 2226 d = self.protocol.messageReceived(stanza) 2227 self.assertFailure(d, error.StanzaError) 2228 d.addCallback(cb) 2229 return d 2230 2231 2232 @implementer(xmppim.IUserSession) 2233 class FakeUserSession(xmppim.UserSession): 2234 2235 def __init__(self, *args, **kwargs): 2236 super(FakeUserSession, self).__init__(*args, **kwargs) 2237 self.broadcastPresenceCalls = [] 2238 2239 2240 def broadcastPresence(self, presence): 2241 self.broadcastPresenceCalls.append(presence) 2242 2243 2244 2245 class FakeUserSessionTest(unittest.TestCase): 2246 """ 2247 Tests for L{FakeUserSessionTest}. 2248 """ 2249 2250 def test_interface(self): 2251 verify.verifyObject(xmppim.IUserSession, FakeUserSession(None)) 2252 2253 2254 2255 class UserSessionPresenceProtocolTest(unittest.TestCase): 2256 """ 2257 Tests for L{xmppim.UserSessionPresenceProtocol}. 2258 """ 2259 2260 def setUp(self): 2261 self.stub = XmlStreamStub() 2262 self.protocol = xmppim.UserSessionPresenceProtocol() 2263 self.protocol.makeConnection(self.stub.xmlstream) 2264 2265 entity = JID(u'user@example.org') 2266 user = xmppim.User(entity) 2267 user.roster = xmppim.InMemoryRoster([]) 2268 2269 self.session = FakeUserSession(user) 2270 self.stub.xmlstream.avatar = self.session 2271 2272 2273 def test_availableReceivedDirected(self): 2274 """ 2275 The directed available stanza is passed for delivery. 2276 """ 2277 stanza = xmppim.AvailabilityPresence( 2278 recipient=JID(u'contact@example.org'), 2279 sender=JID(u'user@example.org'), 2280 available=True) 2281 2282 self.assertFalse(self.protocol.availableReceived(stanza)) 2283 2284 2285 def test_availableReceivedBroadcast(self): 2286 """ 2287 Availabilty is broadcast to contacts and presence is probed. 2288 """ 2289 stanza = xmppim.AvailabilityPresence( 2290 sender=JID(u'user@example.org'), 2291 available=True) 2292 2293 self.assertTrue(self.protocol.availableReceived(stanza)) 2294 self.assertEqual(1, len(self.session.broadcastPresenceCalls)) 2295 presence = self.session.broadcastPresenceCalls[-1] 2296 self.assertIdentical(stanza, presence) 2297 self.assertTrue(presence.available) 2298 2299 2300 def test_unavailableReceivedDirected(self): 2301 """ 2302 The directed available stanza is passed for delivery. 2303 """ 2304 stanza = xmppim.AvailabilityPresence( 2305 recipient=JID(u'contact@example.org'), 2306 sender=JID(u'user@example.org'), 2307 available=False) 2308 2309 self.assertFalse(self.protocol.unavailableReceived(stanza)) 2310 2311 2312 def test_unavailableReceivedBroadcast(self): 2313 """ 2314 Unavailabilty is broadcast to contacts. 2315 """ 2316 stanza = xmppim.AvailabilityPresence( 2317 sender=JID(u'user@example.org'), 2318 available=False) 2319 2320 self.assertTrue(self.protocol.unavailableReceived(stanza)) 2321 self.assertEqual(1, len(self.session.broadcastPresenceCalls)) 2322 presence = self.session.broadcastPresenceCalls[-1] 2323 self.assertIdentical(stanza, presence) 2324 self.assertFalse(presence.available) 2325 2326 2327 def test_subscribedReceived(self): 2328 """ 2329 The subscription approval stanza is passed for delivery. 2330 """ 2331 stanza = xmppim.SubscriptionPresence( 2332 recipient=JID(u'contact@example.org'), 2333 sender=JID(u'user@example.org')) 2334 stanza.stanzaType = u'subscribed' 2335 2336 self.assertFalse(self.protocol.unsubscribedReceived(stanza)) 2337 2338 2339 def test_subscribedReceivedFullJID(self): 2340 """ 2341 The resource is stripped from full JIDs for subscription approval. 2342 """ 2343 stanza = xmppim.SubscriptionPresence( 2344 recipient=JID(u'contact@example.org/Home'), 2345 sender=JID(u'user@example.org')) 2346 stanza.stanzaType = u'subscribed' 2347 stanza.toElement() 2348 2349 self.protocol.subscribedReceived(stanza) 2350 self.assertEqual(u'contact@example.org', stanza.element['to']) 2351 2352 2353 def test_unsubscribedReceived(self): 2354 """ 2355 The subscription cancellation stanza is passed for delivery. 2356 """ 2357 stanza = xmppim.SubscriptionPresence( 2358 recipient=JID(u'contact@example.org'), 2359 sender=JID(u'user@example.org')) 2360 stanza.stanzaType = u'unsubscribed' 2361 2362 self.assertFalse(self.protocol.unsubscribedReceived(stanza)) 2363 2364 2365 def test_unsubscribedReceivedFullJID(self): 2366 """ 2367 The resource is stripped from full JIDs for subscription cancellation. 2368 """ 2369 stanza = xmppim.SubscriptionPresence( 2370 recipient=JID(u'contact@example.org/Home'), 2371 sender=JID(u'user@example.org')) 2372 stanza.stanzaType = u'unsubscribed' 2373 stanza.toElement() 2374 2375 self.protocol.unsubscribedReceived(stanza) 2376 self.assertEqual(u'contact@example.org', stanza.element['to']) 2377 2378 2379 def test_subscribeReceived(self): 2380 """ 2381 The subscribe request stanza is passed for delivery. 2382 """ 2383 stanza = xmppim.SubscriptionPresence( 2384 recipient=JID(u'contact@example.org'), 2385 sender=JID(u'user@example.org')) 2386 stanza.stanzaType = u'subscribe' 2387 2388 self.assertFalse(self.protocol.subscribedReceived(stanza)) 2389 2390 2391 def test_subscribeReceivedFullJID(self): 2392 """ 2393 The resource is stripped from full JIDs for subscribe requests. 2394 """ 2395 stanza = xmppim.SubscriptionPresence( 2396 recipient=JID(u'contact@example.org/Home'), 2397 sender=JID(u'user@example.org')) 2398 stanza.stanzaType = u'subscribe' 2399 stanza.toElement() 2400 2401 self.protocol.subscribeReceived(stanza) 2402 self.assertEqual(u'contact@example.org', stanza.element['to']) 2403 2404 2405 def test_unsubscribeReceived(self): 2406 """ 2407 The unsubscribe request stanza is passed for delivery. 2408 """ 2409 stanza = xmppim.SubscriptionPresence( 2410 recipient=JID(u'contact@example.org'), 2411 sender=JID(u'user@example.org')) 2412 stanza.stanzaType = u'unsubscribe' 2413 2414 self.assertFalse(self.protocol.unsubscribeReceived(stanza)) 2415 2416 2417 def test_unsubscribeReceivedFullJID(self): 2418 """ 2419 The resource is stripped from full JIDs for unsubscribe requests. 2420 """ 2421 stanza = xmppim.SubscriptionPresence( 2422 recipient=JID(u'contact@example.org/Home'), 2423 sender=JID(u'user@example.org')) 2424 stanza.stanzaType = u'unsubscribe' 2425 stanza.toElement() 2426 2427 self.protocol.unsubscribeReceived(stanza) 2428 self.assertEqual(u'contact@example.org', stanza.element['to']) 2429 2430 2431 def test_probeReceived(self): 2432 """ 2433 The probe stanza is passed for delivery. 2434 """ 2435 stanza = xmppim.ProbePresence( 2436 recipient=JID(u'contact@example.org'), 2437 sender=JID(u'user@example.org')) 2438 2439 self.assertFalse(self.protocol.probeReceived(stanza)) 2440 2441 -
wokkel/xmppim.py
diff --git a/wokkel/xmppim.py b/wokkel/xmppim.py
a b 417 417 418 418 419 419 420 def parsePresence(self, element): 421 stanza = Stanza.fromElement(element) 422 423 presenceType = stanza.stanzaType or 'available' 424 425 try: 426 parser = self.presenceTypeParserMap[presenceType] 427 except KeyError: 428 return 429 430 return parser.fromElement(element) 431 432 433 @asyncObserver 420 434 def _onPresence(self, element): 421 435 """ 422 436 Called when a presence stanza has been received. 423 437 """ 424 stanza = Stanza.fromElement(element) 425 426 presenceType = stanza.stanzaType or 'available' 427 428 try: 429 parser = self.presenceTypeParserMap[presenceType] 430 except KeyError: 431 return 432 433 presence = parser.fromElement(element) 438 presence = self.parsePresence(element) 439 presenceType = presence.stanzaType or 'available' 434 440 435 441 try: 436 442 handler = getattr(self, '%sReceived' % presenceType) 437 443 except AttributeError: 438 return 444 return False 439 445 else: 440 handler(presence)446 return handler(presence) 441 447 442 448 443 449 … … 1188 1194 L{Message}. 1189 1195 """ 1190 1196 1197 observerPriority = 0 1191 1198 messageFactory = Message 1192 1199 1193 1200 def connectionInitialized(self): 1194 self.xmlstream.addObserver("/message", self._onMessage) 1201 self.xmlstream.addObserver("/message", self._onMessage, 1202 self.observerPriority) 1195 1203 1196 1204 1197 1205 @asyncObserver … … 1693 1701 presence = ProbePresence(recipient=entity, 1694 1702 sender=user.entity) 1695 1703 self.routeOrDeliver(presence.toElement()) 1704 1705 1706 1707 class AccountIQHandler(XMPPHandler): 1708 1709 def __init__(self, realm): 1710 XMPPHandler.__init__(self) 1711 self.realm = realm 1712 1713 1714 def connectionMade(self): 1715 self.xmlstream.addObserver('/iq', self._onIQ, 1) 1716 1717 1718 @asyncObserver 1719 def _onIQ(self, element): 1720 stanza = Stanza.fromElement(element) 1721 return self.iqReceived(stanza) 1722 1723 1724 def iqReceived(self, iq): 1725 """ 1726 Handler for iq stazas to user accounts' connected resources. 1727 1728 If the recipient is a bare JID or there is no associated user, this 1729 handler ignores the stanza, so that other handlers have a chance 1730 to pick it up. If used, L{generic.FallbackHandler} will respond with a 1731 C{'service-unavailable'} stanza error if no other handlers handle 1732 the iq. 1733 """ 1734 if not iq.recipient.user: 1735 # This is not for a user. 1736 return False 1737 elif not iq.recipient.resource: 1738 # This might be picked up by another handler. 1739 return False 1740 1741 def gotUser(user): 1742 user.deliverIQ(iq) 1743 1744 def notFound(failure): 1745 failure.trap(NoSuchResource, NoSuchUser) 1746 raise error.StanzaError('service-unavailable') 1747 1748 d = self.realm.lookupUser(iq.recipient.userhostJID()) 1749 d.addCallback(gotUser) 1750 d.addErrback(notFound) 1751 return d 1752 1753 1754 1755 class AccountMessageHandler(MessageProtocol): 1756 1757 observerPriority = 1 1758 1759 def __init__(self, realm): 1760 MessageProtocol.__init__(self) 1761 self.realm = realm 1762 1763 1764 def messageReceived(self, message): 1765 """ 1766 Handler for message stanzas to user accounts. 1767 """ 1768 if not message.recipient.user: 1769 # This is not for a user. 1770 return False 1771 1772 def gotUser(user): 1773 user.deliverMessage(message) 1774 1775 def eb(failure): 1776 failure.trap(NoSuchUser, NoSuchResource, NotImplementedError) 1777 raise error.StanzaError('service-unavailable') 1778 1779 d = self.realm.lookupUser(message.recipient.userhostJID()) 1780 d.addCallback(gotUser) 1781 d.addErrback(eb) 1782 return d 1783 1784 1785 1786 class AccountPresenceHandler(PresenceProtocol): 1787 1788 def __init__(self, realm): 1789 PresenceProtocol.__init__(self) 1790 self.realm = realm 1791 1792 1793 def _deliverPresence(self, presence): 1794 if not presence.recipient.user: 1795 # This is not for a user. 1796 return False 1797 1798 def gotUser(user): 1799 user.deliverPresence(presence) 1800 1801 def notFound(failure): 1802 failure.trap(NoSuchResource, NoSuchUser) 1803 return 1804 1805 d = self.realm.lookupUser(presence.recipient.userhostJID()) 1806 d.addCallback(gotUser) 1807 d.addErrback(notFound) 1808 return d 1809 1810 1811 def availableReceived(self, presence): 1812 return self._deliverPresence(presence) 1813 1814 1815 def unavailableReceived(self, presence): 1816 return self._deliverPresence(presence) 1817 1818 1819 def subscribedReceived(self, presence): 1820 log.msg("%r subscribed %s to its presence" % (presence.sender, 1821 presence.recipient)) 1822 return self._deliverPresence(presence) 1823 1824 1825 def unsubscribedReceived(self, presence): 1826 log.msg("%r unsubscribed %s from its presence" % (presence.sender, 1827 presence.recipient)) 1828 return self._deliverPresence(presence) 1829 1830 1831 def subscribeReceived(self, presence): 1832 log.msg("%r requests subscription to %s" % (presence.sender, 1833 presence.recipient)) 1834 return self._deliverPresence(presence) 1835 1836 1837 def unsubscribeReceived(self, presence): 1838 log.msg("%r requests unsubscription from %s" % (presence.sender, 1839 presence.recipient)) 1840 return self._deliverPresence(presence) 1841 1842 1843 def probeReceived(self, presence): 1844 if not presence.recipient.user: 1845 # This is not for a user. 1846 return False 1847 1848 def gotUser(user): 1849 return user.getPresences(presence.sender.userhostJID()) 1850 1851 def notSubscribed(failure): 1852 failure.trap(NoSuchUser, NotSubscribed) 1853 response = SubscriptionPresence(recipient=presence.sender, 1854 sender=presence.recipient) 1855 response.stanzaType = 'unsubscribed' 1856 return [response] 1857 1858 def sendPresences(userPresences): 1859 for userPresence in userPresences: 1860 self.parent.multicast(userPresence, [presence.sender]) 1861 1862 d = self.realm.lookupUser(presence.recipient.userhostJID()) 1863 d.addCallback(gotUser) 1864 d.addErrback(notSubscribed) 1865 d.addCallback(sendPresences) 1866 return d 1867 1868 1869 1870 class UserSessionPresenceProtocol(PresenceProtocol): 1871 """ 1872 Presence protocol for user client sessions. 1873 """ 1874 1875 def _stampContactJID(self, presence): 1876 """ 1877 Stamp the contact JID to be a bare JID. 1878 """ 1879 if presence.recipient.resource: 1880 contactJID = presence.recipient.userhostJID() 1881 presence.element['to'] = contactJID.userhost() 1882 1883 1884 def availableReceived(self, presence): 1885 if presence.recipient: 1886 # Pass through directed presence. 1887 return False 1888 else: 1889 session = self.xmlstream.avatar 1890 session.broadcastPresence(presence) 1891 return True 1892 1893 1894 def unavailableReceived(self, presence): 1895 if presence.recipient: 1896 # Pass through directed presence. 1897 return False 1898 else: 1899 session = self.xmlstream.avatar 1900 session.broadcastPresence(presence) 1901 return True 1902 1903 1904 def subscribedReceived(self, presence): 1905 """ 1906 Subscription approval confirmation was received. 1907 """ 1908 self._stampContactJID(presence) 1909 return False 1910 1911 1912 def unsubscribedReceived(self, presence): 1913 """ 1914 Unsubscription confirmation was received. 1915 """ 1916 self._stampContactJID(presence) 1917 return False 1918 1919 1920 def subscribeReceived(self, presence): 1921 """ 1922 Subscription request was received. 1923 """ 1924 self._stampContactJID(presence) 1925 return False 1926 1927 1928 def unsubscribeReceived(self, presence): 1929 """ 1930 Unsubscription request was received. 1931 """ 1932 self._stampContactJID(presence) 1933 return False 1934 1935 1936 def probeReceived(self, presence): 1937 """ 1938 Probe presence was received. 1939 """ 1940 return False
Note: See TracBrowser
for help on using the repository browser.