Changeset 59:90b972ef16fc in ralphm-patches
- Timestamp:
- Apr 29, 2012, 10:47:45 PM (10 years ago)
- Branch:
- default
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
roster_item.patch
r58 r59 7 7 * Now has `fromElement` and `toElement` methods. 8 8 9 `RosterRequest`: 10 * New class to represent roster request stanzas. 11 9 12 `RosterClientProtocol`: 10 13 * Roster returned from `getRoster` is now indexed by `JID`s (instead of 11 14 the `unicode` representation of the JID. 15 * Outgoing requests are now done using `RosterRequest`. 12 16 * `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 20 TODO: 21 * Add tests for RosterRequest. Version has no coverage. 22 * Add version support when sending. 14 23 15 24 diff --git a/wokkel/test/test_xmppim.py b/wokkel/test/test_xmppim.py 16 25 --- a/wokkel/test/test_xmppim.py 17 26 +++ 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 @@ 26 28 27 29 from wokkel import xmppim 28 30 from wokkel.generic import ErrorStanza, parseXml 29 31 -from wokkel.test.helpers import XmlStreamStub 32 +from wokkel.subprotocols import XMPPHandler 30 33 +from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub 31 34 32 35 NS_XML = 'http://www.w3.org/XML/1998/namespace' 33 36 NS_ROSTER = 'jabber:iq:roster' 34 @@ -449,23 +450,3 60@@37 @@ -449,23 +450,373 @@ 35 38 36 39 … … 374 377 + 375 378 + 379 +class RosterRequestTest(unittest.TestCase): 380 + """ 381 + Tests for L{xmppim.RosterRequest}. 382 + """ 383 + 384 + 376 385 +class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin): 377 386 """ … … 384 393 - self.protocol.xmlstream = self.stub.xmlstream 385 394 - self.protocol.connectionInitialized() 395 + self.patch(XMPPHandler, 'request', self.request) 386 396 + self.service = xmppim.RosterClientProtocol() 387 397 + self.service.makeConnection(self.stub.xmlstream) 388 398 + self.service.connectionInitialized() 399 + 400 + 401 + def request(self, request): 402 + element = request.toElement() 403 + self.stub.xmlstream.send(element) 404 + return defer.Deferred() 389 405 390 406 … … 398 414 # Inspect outgoing iq request 399 415 400 @@ -479,10 +8 17,128 @@416 @@ -479,10 +830,128 @@ 401 417 self.assertEquals(1, len(children)) 402 418 child = children[0] … … 408 424 409 425 response = toResponse(iq, 'result') 410 self.stub.send(response) 426 - self.stub.send(response) 427 + d.callback(response) 411 428 return d 412 429 + … … 433 450 + item['jid'] = 'user@example.org' 434 451 + 435 + self.stub.send(response)452 + d.callback(response) 436 453 + return d 437 454 + … … 463 480 + "wokkel.xmppim.RosterClientProtocol.onRosterSet " 464 481 + "is deprecated. " 465 + "Use RosterClientProtocol.push Receivedinstead.",482 + "Use RosterClientProtocol.push instead.", 466 483 + xmppim.__file__, 467 484 + self.handleRequest, xml) … … 495 512 + "wokkel.xmppim.RosterClientProtocol.onRosterRemove " 496 513 + "is deprecated. " 497 + "Use RosterClientProtocol.push Receivedinstead.",514 + "Use RosterClientProtocol.push instead.", 498 515 + xmppim.__file__, 499 516 + self.handleRequest, xml) … … 502 519 + 503 520 + 504 + def test_push Received(self):505 + """ 506 + A roster push causes push Received 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. 507 524 + """ 508 525 + xml = """ … … 514 531 + """ 515 532 + 516 + items = []517 + 518 + def push Received(item):519 + items.append(item)533 + requests = [] 534 + 535 + def push(request): 536 + requests.append(request) 520 537 + 521 538 + def cb(result): 522 + self.assertEquals(1, len( items), "pushReceivedwas not called")523 + self.assertEquals(JID('user@example.org'), items[0].entity)524 + 525 + self.service.push Received = pushReceived539 + 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 526 543 + 527 544 + d = self.handleRequest(xml) … … 531 548 --- a/wokkel/xmppim.py 532 549 +++ b/wokkel/xmppim.py 533 @@ -7,21 +7,2 8@@550 @@ -7,21 +7,26 @@ 534 551 XMPP IM protocol support. 535 552 … … 544 561 + 545 562 +from twisted.internet import defer 546 +from twisted.words.protocols.jabber import error547 563 from twisted.words.protocols.jabber.jid import JID 548 564 from twisted.words.xish import domish 549 565 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 552 569 +from wokkel.subprotocols import IQHandlerMixin 553 570 from wokkel.subprotocols import XMPPHandler … … 563 580 def __init__(self, to=None, type=None): 564 581 domish.Element.__init__(self, (None, "presence")) 565 @@ -605,8 +61 2,8 @@582 @@ -605,8 +610,8 @@ 566 583 567 584 This represents one contact from an XMPP contact list known as roster. … … 574 591 @type name: C{unicode} 575 592 @ivar subscriptionTo: Subscription state to contact's presence. If C{True}, 576 @@ -616,45 +62 3,137@@593 @@ -616,45 +621,172 @@ 577 594 @ivar subscriptionFrom: Contact's subscription state. If C{True}, the 578 595 contact is subscribed to the presence information … … 709 726 + 710 727 + 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 + 711 763 +class RosterClientProtocol(XMPPHandler, IQHandlerMixin): 712 764 """ … … 738 790 def getRoster(self): 739 791 """ 740 @@ -668, 8 +767,8@@792 @@ -668,14 +800,13 @@ 741 793 roster = {} 742 794 for element in domish.generateElementsQNamed(result.query.children, … … 749 801 return roster 750 802 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} 752 814 """ 753 754 815 - iq = IQ(self.xmlstream, 'set') 816 - iq.addElement((NS_ROSTER, 'query')) 755 817 - item = iq.query.addElement('item') 756 818 - item['jid'] = entity.full() 757 819 - 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) 762 825 763 826 … … 766 829 - iq.hasAttribute('from') and iq['from'] != self.xmlstream: 767 830 - return 768 + item = RosterItem.fromElement(iq.query.item)831 + request = RosterRequest.fromElement(iq) 769 832 770 833 - iq.handled = True 771 + d = defer.maybeDeferred(self.push Received, item)834 + d = defer.maybeDeferred(self.push, request) 772 835 + return d 773 836 … … 776 839 - if unicode(itemElement['subscription']) == 'remove': 777 840 - 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: 779 865 - item = self._parseRosterItem(iq.query.item) 780 866 - self.onRosterSet(item) 781 + def pushReceived(self, item): 782 + """ 783 + Called when a roster push was received. 784 867 - 785 868 - def onRosterSet(self, item): 786 869 - """ 787 870 - 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 - """ 797 875 - 798 876 - def onRosterRemove(self, entity): … … 803 881 - @type entity: L{JID} 804 882 - """ 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:814 883 + if hasattr(self, 'onRosterSet'): 815 884 + warnings.warn( 816 885 + "wokkel.xmppim.RosterClientProtocol.onRosterSet " 817 886 + "is deprecated. " 818 + "Use RosterClientProtocol.push Receivedinstead.",887 + "Use RosterClientProtocol.push instead.", 819 888 + DeprecationWarning) 820 889 + return defer.maybeDeferred(self.onRosterSet, item) -
roster_item_sender.patch
r58 r59 1 1 # HG changeset patch 2 # Parent c69d15fefcece37b5eb00e8ce1db144d6ac98d222 # Parent a37017f764f8fd1f02f18b05d3a7e0a1e7973c33 3 3 Record sender on received roster sets to support alternative roster sources. 4 4 … … 11 11 --- a/wokkel/test/test_xmppim.py 12 12 +++ b/wokkel/test/test_xmppim.py 13 @@ -931,6 +931,8 @@ 14 items = [] 13 @@ -7,6 +7,7 @@ 15 14 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) 20 28 21 29 def cb(result): 22 @@ -9 42,3 +944,66 @@30 @@ -955,3 +958,66 @@ 23 31 d = self.handleRequest(xml) 24 32 d.addCallback(cb) … … 26 34 + 27 35 + 28 + def test_push ReceivedOtherSource(self):36 + def test_pushOtherSource(self): 29 37 + """ 30 38 + Roster pushes can be sent from other entities, too. … … 38 46 + """ 39 47 + 40 + items = []48 + requests = [] 41 49 + 42 + def onRosterSet(item):43 + items.append(item)50 + def push(request): 51 + requests.append(request) 44 52 + 45 53 + 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) 49 57 + 50 + self.service. onRosterSet = onRosterSet58 + self.service.push = push 51 59 + 52 60 + d = self.handleRequest(xml) … … 67 75 + """ 68 76 + 69 + def push Received(item):77 + def push(request): 70 78 + """ 71 79 + Explicitly ignore pushes from other entities. … … 74 82 + should hold for regular XMPP client connections. 75 83 + """ 76 + if ( item.sender and77 + item.sender.userhost() != item.recipient.userhost()):84 + if (request.sender and 85 + request.sender.userhost() != request.recipient.userhost()): 78 86 + raise xmppim.RosterPushIgnored() 79 87 + … … 81 89 + self.assertEquals('service-unavailable', result.condition) 82 90 + 83 + self.service.push Received = pushReceived91 + self.service.push = push 84 92 + 85 93 + d = self.handleRequest(xml) … … 90 98 --- a/wokkel/xmppim.py 91 99 +++ 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 103 102 104 __subscriptionStates = {(False, False): None,105 @@ -653,6 +659,9 @@ 106 self.approved = False107 self.remove = False103 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 108 107 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 @@ 116 109 117 110 … … 127 120 """ 128 121 Client side XMPP roster protocol. 129 @@ - 796,9 +812,20@@122 @@ -826,9 +834,14 @@ 130 123 131 124 … … 135 128 + raise error.StanzaError('service-unavailable') 136 129 + 137 + # Parse received roster item. 138 item = RosterItem.fromElement(iq.query.item) 130 request = RosterRequest.fromElement(iq) 139 131 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) 146 133 + d.addErrback(trapIgnored) 147 134 return d 148 135 149 136 150 @@ -8 08,6 +835,16 @@137 @@ -838,6 +851,16 @@ 151 138 152 139 Override this to handle roster pushes. … … 155 142 + hold a roster for that user. However, how a client should deal with 156 143 + 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 but158 + 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. 159 146 + 160 147 + To avert presence leaks, a handler can raise L{RosterPushIgnored} when
Note: See TracChangeset
for help on using the changeset viewer.