Changeset 59:90b972ef16fc in ralphm-patches


Ignore:
Timestamp:
Apr 29, 2012, 10:47:45 PM (9 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Message:

Add RosterRequest? and use it throughout RosterClientProtocol?.

This also renames pushReceived to push, which now gets a RosterRequest? as
its sole argument. This allows for the sender and recipient addressing to
remain on the request, and not on the item. As a bonus, support for roster
versioning can now be added transparently.

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • roster_item.patch

    r58 r59  
    77 * Now has `fromElement` and `toElement` methods.
    88
     9`RosterRequest`:
     10 * New class to represent roster request stanzas.
     11
    912`RosterClientProtocol`:
    1013 * Roster returned from `getRoster` is now indexed by `JID`s (instead of
    1114   the `unicode` representation of the JID.
     15 * Outgoing requests are now done using `RosterRequest`.
    1216 * `onRosterSet` and `onRosterRemove` are deprecated in favor of
    13    `pushReceived`, which is called with a `RosterItem` for all roster pushes.
     17   `pushReceived`, which is called with a `RosterRequest` for all roster
     18   pushes.
     19
     20TODO:
     21 * Add tests for RosterRequest. Version has no coverage.
     22 * Add version support when sending.
    1423
    1524diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py
    1625--- a/wokkel/test/test_xmppim.py
    1726+++ b/wokkel/test/test_xmppim.py
    18 @@ -7,13 +7,14 @@
    19  
    20  from twisted.internet import defer
    21  from twisted.trial import unittest
    22 +from twisted.words.protocols.jabber import error
    23  from twisted.words.protocols.jabber.jid import JID
    24  from twisted.words.protocols.jabber.xmlstream import toResponse
    25  from twisted.words.xish import domish, utility
     27@@ -13,7 +13,8 @@
    2628 
    2729 from wokkel import xmppim
    2830 from wokkel.generic import ErrorStanza, parseXml
    2931-from wokkel.test.helpers import XmlStreamStub
     32+from wokkel.subprotocols import XMPPHandler
    3033+from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub
    3134 
    3235 NS_XML = 'http://www.w3.org/XML/1998/namespace'
    3336 NS_ROSTER = 'jabber:iq:roster'
    34 @@ -449,23 +450,360 @@
     37@@ -449,23 +450,373 @@
    3538 
    3639 
     
    374377+
    375378+
     379+class RosterRequestTest(unittest.TestCase):
     380+    """
     381+    Tests for L{xmppim.RosterRequest}.
     382+    """
     383+
     384+
    376385+class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
    377386     """
     
    384393-        self.protocol.xmlstream = self.stub.xmlstream
    385394-        self.protocol.connectionInitialized()
     395+        self.patch(XMPPHandler, 'request', self.request)
    386396+        self.service = xmppim.RosterClientProtocol()
    387397+        self.service.makeConnection(self.stub.xmlstream)
    388398+        self.service.connectionInitialized()
     399+
     400+
     401+    def request(self, request):
     402+        element = request.toElement()
     403+        self.stub.xmlstream.send(element)
     404+        return defer.Deferred()
    389405 
    390406 
     
    398414         # Inspect outgoing iq request
    399415 
    400 @@ -479,10 +817,128 @@
     416@@ -479,10 +830,128 @@
    401417         self.assertEquals(1, len(children))
    402418         child = children[0]
     
    408424 
    409425         response = toResponse(iq, 'result')
    410          self.stub.send(response)
     426-        self.stub.send(response)
     427+        d.callback(response)
    411428         return d
    412429+
     
    433450+        item['jid'] = 'user@example.org'
    434451+
    435 +        self.stub.send(response)
     452+        d.callback(response)
    436453+        return d
    437454+
     
    463480+                             "wokkel.xmppim.RosterClientProtocol.onRosterSet "
    464481+                             "is deprecated. "
    465 +                             "Use RosterClientProtocol.pushReceived instead.",
     482+                             "Use RosterClientProtocol.push instead.",
    466483+                             xmppim.__file__,
    467484+                             self.handleRequest, xml)
     
    495512+                             "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
    496513+                             "is deprecated. "
    497 +                             "Use RosterClientProtocol.pushReceived instead.",
     514+                             "Use RosterClientProtocol.push instead.",
    498515+                             xmppim.__file__,
    499516+                             self.handleRequest, xml)
     
    502519+
    503520+
    504 +    def test_pushReceived(self):
    505 +        """
    506 +        A roster push causes pushReceived to be called with the parsed item.
     521+    def test_push(self):
     522+        """
     523+        A roster push causes push to be called with the parsed request.
    507524+        """
    508525+        xml = """
     
    514531+        """
    515532+
    516 +        items = []
    517 +
    518 +        def pushReceived(item):
    519 +            items.append(item)
     533+        requests = []
     534+
     535+        def push(request):
     536+            requests.append(request)
    520537+
    521538+        def cb(result):
    522 +            self.assertEquals(1, len(items), "pushReceived was not called")
    523 +            self.assertEquals(JID('user@example.org'), items[0].entity)
    524 +
    525 +        self.service.pushReceived = pushReceived
     539+            self.assertEquals(1, len(requests), "push was not called")
     540+            self.assertEquals(JID('user@example.org'), requests[0].item.entity)
     541+
     542+        self.service.push = push
    526543+
    527544+        d = self.handleRequest(xml)
     
    531548--- a/wokkel/xmppim.py
    532549+++ b/wokkel/xmppim.py
    533 @@ -7,21 +7,28 @@
     550@@ -7,21 +7,26 @@
    534551 XMPP IM protocol support.
    535552 
     
    544561+
    545562+from twisted.internet import defer
    546 +from twisted.words.protocols.jabber import error
    547563 from twisted.words.protocols.jabber.jid import JID
    548564 from twisted.words.xish import domish
    549565 
    550  from wokkel.compat import IQ
    551  from wokkel.generic import ErrorStanza, Stanza
     566-from wokkel.compat import IQ
     567-from wokkel.generic import ErrorStanza, Stanza
     568+from wokkel.generic import ErrorStanza, Stanza, Request
    552569+from wokkel.subprotocols import IQHandlerMixin
    553570 from wokkel.subprotocols import XMPPHandler
     
    563580     def __init__(self, to=None, type=None):
    564581         domish.Element.__init__(self, (None, "presence"))
    565 @@ -605,8 +612,8 @@
     582@@ -605,8 +610,8 @@
    566583 
    567584     This represents one contact from an XMPP contact list known as roster.
     
    574591     @type name: C{unicode}
    575592     @ivar subscriptionTo: Subscription state to contact's presence. If C{True},
    576 @@ -616,45 +623,137 @@
     593@@ -616,45 +621,172 @@
    577594     @ivar subscriptionFrom: Contact's subscription state. If C{True}, the
    578595                             contact is subscribed to the presence information
     
    709726+
    710727+
     728+class RosterRequest(Request):
     729+    """
     730+    Roster request.
     731+
     732+    @ivar item: Roster item to be set or pushed.
     733+    @type item: L{RosterItem}.
     734+
     735+    @ivar version: Roster version identifier for roster pushes and
     736+        retrieving the roster as a delta from a known cached version. This
     737+        should only be set if the recipient is known to support roster
     738+        versioning.
     739+    @type version: C{unicode}
     740+    """
     741+    item = None
     742+    version = None
     743+
     744+    childParsers = {(NS_ROSTER, 'query'): '_childParser_query'}
     745+
     746+    def _childParser_query(self, element):
     747+        self.version = element.getAttribute('ver')
     748+
     749+        for child in element.elements(NS_ROSTER, 'item'):
     750+            self.item = RosterItem.fromElement(child)
     751+            break
     752+
     753+
     754+    def toElement(self):
     755+        element = Request.toElement(self)
     756+        element.addElement((NS_ROSTER, 'query'))
     757+        if self.item:
     758+            element.query.addChild(self.item.toElement())
     759+        return element
     760+
     761+
     762+
    711763+class RosterClientProtocol(XMPPHandler, IQHandlerMixin):
    712764     """
     
    738790     def getRoster(self):
    739791         """
    740 @@ -668,8 +767,8 @@
     792@@ -668,14 +800,13 @@
    741793             roster = {}
    742794             for element in domish.generateElementsQNamed(result.query.children,
     
    749801             return roster
    750802 
    751 @@ -690,42 +789,48 @@
     803-        iq = IQ(self.xmlstream, 'get')
     804-        iq.addElement((NS_ROSTER, 'query'))
     805-        d = iq.send()
     806+        request = RosterRequest(stanzaType='get')
     807+        d = self.request(request)
     808         d.addCallback(processRoster)
     809         return d
     810 
     811@@ -688,44 +819,50 @@
     812         @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
     813         @rtype: L{twisted.internet.defer.Deferred}
    752814         """
    753          iq = IQ(self.xmlstream, 'set')
    754          iq.addElement((NS_ROSTER, 'query'))
     815-        iq = IQ(self.xmlstream, 'set')
     816-        iq.addElement((NS_ROSTER, 'query'))
    755817-        item = iq.query.addElement('item')
    756818-        item['jid'] = entity.full()
    757819-        item['subscription'] = 'remove'
    758 +        item = RosterItem(entity)
    759 +        item.remove = True
    760 +        iq.query.addChild(item.toElement())
    761          return iq.send()
     820-        return iq.send()
     821+        request = RosterRequest(stanzaType='set')
     822+        request.item = RosterItem(entity)
     823+        request.item.remove = True
     824+        return self.request(request)
    762825 
    763826 
     
    766829-           iq.hasAttribute('from') and iq['from'] != self.xmlstream:
    767830-            return
    768 +        item = RosterItem.fromElement(iq.query.item)
     831+        request = RosterRequest.fromElement(iq)
    769832 
    770833-        iq.handled = True
    771 +        d = defer.maybeDeferred(self.pushReceived, item)
     834+        d = defer.maybeDeferred(self.push, request)
    772835+        return d
    773836 
     
    776839-        if unicode(itemElement['subscription']) == 'remove':
    777840-            self.onRosterRemove(JID(itemElement['jid']))
    778 -        else:
     841+    def push(self, request):
     842+        """
     843+        Called when a roster push was received.
     844+
     845+        Override this to handle roster pushes.
     846+
     847+        For backwards compatibility, the default implementation calls
     848+        the deprecated C{onRosterSet} or C{onRosterRemove} if defined on
     849+        C{self}.
     850+
     851+        @param request: The push request.
     852+        @type request: L{RosterRequest}
     853+        """
     854+        item = request.item
     855+
     856+        if item.remove:
     857+            if hasattr(self, 'onRosterRemove'):
     858+                warnings.warn(
     859+                    "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
     860+                    "is deprecated. "
     861+                    "Use RosterClientProtocol.push instead.",
     862+                    DeprecationWarning)
     863+                return defer.maybeDeferred(self.onRosterRemove, item.entity)
     864         else:
    779865-            item = self._parseRosterItem(iq.query.item)
    780866-            self.onRosterSet(item)
    781 +    def pushReceived(self, item):
    782 +        """
    783 +        Called when a roster push was received.
    784  
     867-
    785868-    def onRosterSet(self, item):
    786869-        """
    787870-        Called when a roster push for a new or update item was received.
    788 +        Override this to handle roster pushes.
    789 +
    790 +        For backwards compatibility, the default implementation calls
    791 +        the deprecated C{onRosterSet} or C{onRosterRemove} if defined on
    792 +        C{self}.
    793  
    794          @param item: The pushed roster item.
    795          @type item: L{RosterItem}
    796          """
     871-
     872-        @param item: The pushed roster item.
     873-        @type item: L{RosterItem}
     874-        """
    797875-
    798876-    def onRosterRemove(self, entity):
     
    803881-        @type entity: L{JID}
    804882-        """
    805 +        if item.remove:
    806 +            if hasattr(self, 'onRosterRemove'):
    807 +                warnings.warn(
    808 +                    "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
    809 +                    "is deprecated. "
    810 +                    "Use RosterClientProtocol.pushReceived instead.",
    811 +                    DeprecationWarning)
    812 +                return defer.maybeDeferred(self.onRosterRemove, item.entity)
    813 +        else:
    814883+            if hasattr(self, 'onRosterSet'):
    815884+                warnings.warn(
    816885+                    "wokkel.xmppim.RosterClientProtocol.onRosterSet "
    817886+                    "is deprecated. "
    818 +                    "Use RosterClientProtocol.pushReceived instead.",
     887+                    "Use RosterClientProtocol.push instead.",
    819888+                    DeprecationWarning)
    820889+                return defer.maybeDeferred(self.onRosterSet, item)
  • roster_item_sender.patch

    r58 r59  
    11# HG changeset patch
    2 # Parent c69d15fefcece37b5eb00e8ce1db144d6ac98d22
     2# Parent a37017f764f8fd1f02f18b05d3a7e0a1e7973c33
    33Record sender on received roster sets to support alternative roster sources.
    44
     
    1111--- a/wokkel/test/test_xmppim.py
    1212+++ b/wokkel/test/test_xmppim.py
    13 @@ -931,6 +931,8 @@
    14          items = []
     13@@ -7,6 +7,7 @@
    1514 
    16          def pushReceived(item):
    17 +            self.assertIs(None, item.recipient)
    18 +            self.assertIs(None, item.sender)
    19              items.append(item)
     15 from twisted.internet import defer
     16 from twisted.trial import unittest
     17+from twisted.words.protocols.jabber import error
     18 from twisted.words.protocols.jabber.jid import JID
     19 from twisted.words.protocols.jabber.xmlstream import toResponse
     20 from twisted.words.xish import domish, utility
     21@@ -944,6 +945,8 @@
     22         requests = []
     23 
     24         def push(request):
     25+            self.assertIs(None, request.recipient)
     26+            self.assertIs(None, request.sender)
     27             requests.append(request)
    2028 
    2129         def cb(result):
    22 @@ -942,3 +944,66 @@
     30@@ -955,3 +958,66 @@
    2331         d = self.handleRequest(xml)
    2432         d.addCallback(cb)
     
    2634+
    2735+
    28 +    def test_pushReceivedOtherSource(self):
     36+    def test_pushOtherSource(self):
    2937+        """
    3038+        Roster pushes can be sent from other entities, too.
     
    3846+        """
    3947+
    40 +        items = []
     48+        requests = []
    4149+
    42 +        def onRosterSet(item):
    43 +            items.append(item)
     50+        def push(request):
     51+            requests.append(request)
    4452+
    4553+        def cb(result):
    46 +            item = items[0]
    47 +            self.assertEquals(JID('this@example.org/Home'), item.recipient)
    48 +            self.assertEquals(JID('other@example.org'), item.sender)
     54+            request = requests[0]
     55+            self.assertEquals(JID('this@example.org/Home'), request.recipient)
     56+            self.assertEquals(JID('other@example.org'), request.sender)
    4957+
    50 +        self.service.onRosterSet = onRosterSet
     58+        self.service.push = push
    5159+
    5260+        d = self.handleRequest(xml)
     
    6775+        """
    6876+
    69 +        def pushReceived(item):
     77+        def push(request):
    7078+            """
    7179+            Explicitly ignore pushes from other entities.
     
    7482+            should hold for regular XMPP client connections.
    7583+            """
    76 +            if (item.sender and
    77 +                item.sender.userhost() != item.recipient.userhost()):
     84+            if (request.sender and
     85+                request.sender.userhost() != request.recipient.userhost()):
    7886+                raise xmppim.RosterPushIgnored()
    7987+
     
    8189+            self.assertEquals('service-unavailable', result.condition)
    8290+
    83 +        self.service.pushReceived = pushReceived
     91+        self.service.push = push
    8492+
    8593+        d = self.handleRequest(xml)
     
    9098--- a/wokkel/xmppim.py
    9199+++ b/wokkel/xmppim.py
    92 @@ -634,6 +634,12 @@
    93      @type approved: C{bool}
    94      @ivar remove: Signals roster item removal.
    95      @type remove: C{bool}
    96 +
    97 +    @ivar recipient: The JID of the recipient of this roster item.
    98 +    @type recipient: L{JID}
    99 +
    100 +    @ivar sender: The JID of the sender of this roster item.
    101 +    @type sender: L{JID}
    102      """
     100@@ -13,6 +13,7 @@
     101 import warnings
    103102 
    104      __subscriptionStates = {(False, False): None,
    105 @@ -653,6 +659,9 @@
    106          self.approved = False
    107          self.remove = False
     103 from twisted.internet import defer
     104+from twisted.words.protocols.jabber import error
     105 from twisted.words.protocols.jabber.jid import JID
     106 from twisted.words.xish import domish
    108107 
    109 +        self.sender = None
    110 +        self.recipient = None
    111 +
    112  
    113      def __getJID(self):
    114          warnings.warn(
    115 @@ -743,6 +752,13 @@
     108@@ -776,6 +777,13 @@
    116109 
    117110 
     
    127120     """
    128121     Client side XMPP roster protocol.
    129 @@ -796,9 +812,20 @@
     122@@ -826,9 +834,14 @@
    130123 
    131124 
     
    135128+            raise error.StanzaError('service-unavailable')
    136129+
    137 +        # Parse received roster item.
    138          item = RosterItem.fromElement(iq.query.item)
     130         request = RosterRequest.fromElement(iq)
    139131 
    140 +        # Copy stanza addressing to roster item.
    141 +        request = Stanza.fromElement(iq)
    142 +        item.recipient = request.recipient
    143 +        item.sender = request.sender
    144 +
    145          d = defer.maybeDeferred(self.pushReceived, item)
     132         d = defer.maybeDeferred(self.push, request)
    146133+        d.addErrback(trapIgnored)
    147134         return d
    148135 
    149136 
    150 @@ -808,6 +835,16 @@
     137@@ -838,6 +851,16 @@
    151138 
    152139         Override this to handle roster pushes.
     
    155142+        hold a roster for that user. However, how a client should deal with
    156143+        that is currently not yet specfied. For now, it is advisable to ignore
    157 +        roster pushes from other entities. I.e. when C{item.sender} is set but
    158 +        the sender's bare JID is different from the user's bare JID.
     144+        roster pushes from other entities. I.e. when C{request.sender} is set
     145+        but the sender's bare JID is different from the user's bare JID.
    159146+
    160147+        To avert presence leaks, a handler can raise L{RosterPushIgnored} when
Note: See TracChangeset for help on using the changeset viewer.