source: ralphm-patches/roster_server.patch

Last change on this file was 73:f574beee3bca, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Minor cleanups, improved error message handling, and upstreamed patch.

  • Upstreamed deprecation of prepareIDNName.
  • MessageProtocol? now properly handles error message stanzas.
  • Renamed methods for RosterServerProtocol?.
  • Prefer connectionInitialized for setting up stanza observers.
File size: 7.1 KB
RevLine 
[51]1Add server side support for the roster protocol.
2
[72]3 * Implements roster get by calling `getRoster` and using the returned
4   `Roster` to send back the roster.
[54]5
6TODO:
7 * Add support for roster sets?
8
[62]9diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py
10--- a/wokkel/test/test_xmppim.py
11+++ b/wokkel/test/test_xmppim.py
[72]12@@ -1339,6 +1339,85 @@
13 
14 
15 
[51]16+class RosterServerProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
17+    """
[62]18+    Tests for L{xmppim.RosterServerProtocol}.
[51]19+    """
20+
21+    def setUp(self):
22+        self.stub = XmlStreamStub()
[62]23+        self.service = xmppim.RosterServerProtocol()
[51]24+        self.service.makeConnection(self.stub.xmlstream)
25+        self.service.connectionInitialized()
26+
27+
[73]28+    def test_getRosterReceived(self):
[51]29+        """
30+        A roster get request should trigger getRoster with the request.
31+        """
32+        xml = """
[73]33+          <iq type='get' from='user@example.org/Home'>
[51]34+            <query xmlns='jabber:iq:roster'/>
35+          </iq>
36+        """
37+
[73]38+        def getRosterReceived(request):
39+            self.assertEqual(JID('user@example.org/Home'), request.sender)
[62]40+            item = xmppim.RosterItem(JID('other@example.org'), True, False,
[51]41+                                 'The User')
[72]42+            roster = xmppim.Roster({item.entity: item})
43+            return defer.succeed(roster)
[51]44+
45+        def cb(element):
46+            self.assertEquals('query', element.name)
47+            self.assertEquals(NS_ROSTER, element.uri)
48+            itemElements = list(domish.generateElementsQNamed(
49+                element.children, 'item', NS_ROSTER))
50+            self.assertEqual(1, len(itemElements))
51+            item = itemElements[0]
52+            self.assertEquals(u'other@example.org', item.getAttribute('jid'))
53+            self.assertEquals(u'to', item.getAttribute('subscription'))
54+            self.assertEquals(u'The User', item.getAttribute('name'))
55+
[73]56+        self.service.getRosterReceived = getRosterReceived
[51]57+        d = self.handleRequest(xml)
58+        d.addCallback(cb)
59+        return d
60+
61+
[73]62+    def test_getRosterReceivedNotOverridden(self):
[51]63+        """
64+        If getRoster is not overridden, return feature-not-implemented.
65+        """
66+        xml = """
[73]67+          <iq type='get' from='user@example.org/Home'>
[51]68+            <query xmlns='jabber:iq:roster'/>
69+          </iq>
70+        """
71+
72+        d = self.handleRequest(xml)
73+        self.assertFailure(d, NotImplementedError)
74+        return d
75+
76+
[73]77+    def test_setRosterReceived(self):
[51]78+        """
79+        Roster set is not yet supported.
80+        """
81+        xml = """
[73]82+          <iq type='set' from='user@example.org/Home'>
[51]83+            <query xmlns='jabber:iq:roster'>
84+              <item jid='other@example.org' name='Other User'/>
85+            </query>
86+          </iq>
87+        """
88+
89+        d = self.handleRequest(xml)
90+        self.assertFailure(d, NotImplementedError)
91+        return d
[72]92+
93+
94+
95 class MessageTest(unittest.TestCase):
96     """
97     Tests for L{xmppim.Message}.
[62]98diff --git a/wokkel/xmppim.py b/wokkel/xmppim.py
99--- a/wokkel/xmppim.py
100+++ b/wokkel/xmppim.py
[72]101@@ -26,6 +26,7 @@
[62]102 NS_ROSTER = 'jabber:iq:roster'
103 
104 XPATH_ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER
105+XPATH_ROSTER_GET = "/iq[@type='get']/query[@xmlns='%s']" % NS_ROSTER
106 
107 
108 
[72]109@@ -739,9 +740,9 @@
110 
111 
112     @classmethod
113-    def fromElement(Class, element):
114+    def fromElement(cls, element):
115         entity = JID(element['jid'])
116-        item = Class(entity)
117+        item = cls(entity)
118         subscription = element.getAttribute('subscription')
119         if subscription == 'remove':
120             item.remove = True
121@@ -780,21 +781,26 @@
122     version = None
123     rosterSet = False
124 
[62]125+
[72]126     def parseRequest(self, element):
127-        self.version = element.getAttribute('ver')
128-
129-        for child in element.elements(NS_ROSTER, 'item'):
130-            self.item = RosterItem.fromElement(child)
131+        roster = Roster.fromElement(element)
132+        self.version = roster.version
133+        for item in roster.itervalues():
134+            self.item = item
135             break
136+        return roster
137 
138 
139     def toElement(self):
140         element = Request.toElement(self)
141-        query = element.addElement((NS_ROSTER, 'query'))
142-        if self.version is not None:
143-            query['ver'] = self.version
[62]144+
[72]145+        roster = Roster()
146+        roster.version = self.version
147         if self.item:
148-            query.addChild(self.item.toElement(rosterSet=self.rosterSet))
149+            roster[self.item.entity] = self.item
[62]150+
[72]151+        element.addChild(roster.toElement(rosterSet=self.rosterSet))
152+
153         return element
154 
155 
156@@ -812,7 +818,7 @@
157 
158 class Roster(dict):
159     """
160-    In-memory roster container.
161+    Roster container.
162 
163     This provides a roster as a mapping from L{JID} to L{RosterItem}. If
164     roster versioning is used, the C{version} attribute holds the version
165@@ -825,6 +831,28 @@
166     version = None
167 
168 
169+    @classmethod
170+    def fromElement(cls, element):
171+        roster = cls()
172+        roster.version = element.getAttribute('ver')
173+        for element in element.elements(NS_ROSTER, 'item'):
174+            item = RosterItem.fromElement(element)
175+            roster[item.entity] = item
176+        return roster
177+
178+
179+    def toElement(self, rosterSet=False):
180+        element = domish.Element((NS_ROSTER, 'query'))
181+
182+        if self.version:
183+            element['ver'] = self.version
184+
185+        for item in self.itervalues():
186+            element.addChild(item.toElement(rosterSet))
187+
188+        return element
189+
190+
191 
192 class RosterClientProtocol(XMPPHandler, IQHandlerMixin):
193     """
194@@ -898,12 +926,7 @@
195 
196         def processRoster(result):
197             if result.query is not None:
198-                roster = Roster()
199-                roster.version = result.query.getAttribute('ver')
200-                for element in result.query.elements(NS_ROSTER, 'item'):
201-                    item = RosterItem.fromElement(element)
202-                    roster[item.entity] = item
203-                return roster
204+                return Roster.fromElement(result.query)
205             else:
206                 return None
207 
208@@ -1002,6 +1025,43 @@
209 
210 
211 
[62]212+class RosterServerProtocol(XMPPHandler, IQHandlerMixin):
213+    """
214+    XMPP subprotocol handler for the roster, server side.
215+    """
216+
217+    iqHandlers = {
218+            XPATH_ROSTER_GET: '_onRosterGet',
219+            # XPATH_ROSTER_SET: '_onRosterSet',
220+            }
221+
222+    def connectionInitialized(self):
223+        self.xmlstream.addObserver(XPATH_ROSTER_GET, self.handleRequest)
224+        self.xmlstream.addObserver(XPATH_ROSTER_SET, self.handleRequest)
225+
226+
[72]227+    def _onRosterGet(self, iq):
228+        request = RosterRequest.fromElement(iq)
[62]229+
[72]230+        def toResponse(roster):
231+            return roster.toElement()
[62]232+
[73]233+        d = self.getRosterReceived(request)
[72]234+        d.addCallback(toResponse)
[62]235+        return d
236+
237+
[73]238+    def getRosterReceived(self, request):
[72]239+        """
240+        Called when the roster is requested.
241+
242+        @returns: Deferred that fires with a L{Roster}.
243+        @rtype: L{defer.Deferred}
244+        """
[62]245+        raise NotImplementedError()
[72]246+
247+
248+
249 class Message(Stanza):
250     """
251     A message stanza.
Note: See TracBrowser for help on using the repository browser.