Changeset 172:ea2774e1c71c for wokkel
- Timestamp:
- May 9, 2012, 2:24:28 PM (10 years ago)
- Branch:
- default
- Location:
- wokkel
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
wokkel/test/test_xmppim.py
r96 r172 8 8 from twisted.internet import defer 9 9 from twisted.trial import unittest 10 from twisted.words.protocols.jabber import error 10 11 from twisted.words.protocols.jabber.jid import JID 11 12 from twisted.words.protocols.jabber.xmlstream import toResponse … … 14 15 from wokkel import xmppim 15 16 from wokkel.generic import ErrorStanza, parseXml 16 from wokkel.test.helpers import XmlStreamStub17 from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub 17 18 18 19 NS_XML = 'http://www.w3.org/XML/1998/namespace' … … 450 451 451 452 452 class RosterClientProtocolTest(unittest.TestCase): 453 class RosterItemTest(unittest.TestCase): 454 """ 455 Tests for L{xmppim.RosterItem}. 456 """ 457 458 def test_toElement(self): 459 """ 460 A roster item has the correct namespace/name, lacks unset attributes. 461 """ 462 item = xmppim.RosterItem(JID('user@example.org')) 463 element = item.toElement() 464 self.assertEqual('item', element.name) 465 self.assertEqual(NS_ROSTER, element.uri) 466 self.assertFalse(element.hasAttribute('subscription')) 467 self.assertFalse(element.hasAttribute('ask')) 468 self.assertEqual(u"", element.getAttribute('name', u"")) 469 self.assertFalse(element.hasAttribute('approved')) 470 self.assertEquals(0, len(list(element.elements()))) 471 472 473 def test_toElementMinimal(self): 474 """ 475 A bare roster item only has a jid attribute. 476 """ 477 item = xmppim.RosterItem(JID('user@example.org')) 478 element = item.toElement() 479 self.assertEqual(u'user@example.org', element.getAttribute('jid')) 480 481 482 def test_toElementSubscriptionNone(self): 483 """ 484 A roster item with no subscription has no subscription attribute. 485 """ 486 item = xmppim.RosterItem(JID('user@example.org'), 487 subscriptionTo=False, 488 subscriptionFrom=False) 489 element = item.toElement() 490 self.assertIdentical(None, element.getAttribute('subscription')) 491 492 493 def test_toElementSubscriptionTo(self): 494 """ 495 A roster item with subscriptionTo set has subscription 'to'. 496 """ 497 item = xmppim.RosterItem(JID('user@example.org'), 498 subscriptionTo=True, 499 subscriptionFrom=False) 500 element = item.toElement() 501 self.assertEqual('to', element.getAttribute('subscription')) 502 503 504 def test_toElementSubscriptionFrom(self): 505 """ 506 A roster item with subscriptionFrom set has subscription 'to'. 507 """ 508 item = xmppim.RosterItem(JID('user@example.org'), 509 subscriptionTo=False, 510 subscriptionFrom=True) 511 element = item.toElement() 512 self.assertEqual('from', element.getAttribute('subscription')) 513 514 515 def test_toElementSubscriptionBoth(self): 516 """ 517 A roster item with mutual subscription has subscription 'both'. 518 """ 519 item = xmppim.RosterItem(JID('user@example.org'), 520 subscriptionTo=True, 521 subscriptionFrom=True) 522 element = item.toElement() 523 self.assertEqual('both', element.getAttribute('subscription')) 524 525 526 def test_toElementSubscriptionRemove(self): 527 """ 528 A roster item with remove set has subscription 'remove'. 529 """ 530 item = xmppim.RosterItem(JID('user@example.org')) 531 item.remove = True 532 element = item.toElement() 533 self.assertEqual('remove', element.getAttribute('subscription')) 534 535 536 def test_toElementAsk(self): 537 """ 538 A roster item with pendingOut set has subscription 'ask'. 539 """ 540 item = xmppim.RosterItem(JID('user@example.org')) 541 item.pendingOut = True 542 element = item.toElement() 543 self.assertEqual('subscribe', element.getAttribute('ask')) 544 545 546 def test_toElementName(self): 547 """ 548 A roster item's name is rendered to the 'name' attribute. 549 """ 550 item = xmppim.RosterItem(JID('user@example.org'), 551 name='Joe User') 552 element = item.toElement() 553 self.assertEqual(u'Joe User', element.getAttribute('name')) 554 555 556 def test_toElementGroups(self): 557 """ 558 A roster item's groups are rendered as 'group' child elements. 559 """ 560 groups = set(['Friends', 'Jabber']) 561 item = xmppim.RosterItem(JID('user@example.org'), 562 groups=groups) 563 564 element = item.toElement() 565 foundGroups = set() 566 for child in element.elements(): 567 if child.uri == NS_ROSTER and child.name == 'group': 568 foundGroups.add(unicode(child)) 569 570 self.assertEqual(groups, foundGroups) 571 572 573 def test_toElementApproved(self): 574 """ 575 A pre-approved subscription for a roster item has an 'approved' flag. 576 """ 577 item = xmppim.RosterItem(JID('user@example.org')) 578 item.approved = True 579 element = item.toElement() 580 self.assertEqual(u'true', element.getAttribute('approved')) 581 582 583 def test_fromElementMinimal(self): 584 """ 585 A minimal roster item has a reference to the JID of the contact. 586 """ 587 588 xml = """ 589 <item xmlns="jabber:iq:roster" 590 jid="test@example.org"/> 591 """ 592 593 item = xmppim.RosterItem.fromElement(parseXml(xml)) 594 self.assertEqual(JID(u"test@example.org"), item.entity) 595 self.assertEqual(u"", item.name) 596 self.assertFalse(item.subscriptionTo) 597 self.assertFalse(item.subscriptionFrom) 598 self.assertFalse(item.pendingOut) 599 self.assertFalse(item.approved) 600 self.assertEqual(set(), item.groups) 601 602 603 def test_fromElementName(self): 604 """ 605 A roster item may have an optional name. 606 """ 607 608 xml = """ 609 <item xmlns="jabber:iq:roster" 610 jid="test@example.org" 611 name="Test User"/> 612 """ 613 614 item = xmppim.RosterItem.fromElement(parseXml(xml)) 615 self.assertEqual(u"Test User", item.name) 616 617 618 def test_fromElementGroups(self): 619 """ 620 A roster item may have one or more groups. 621 """ 622 623 xml = """ 624 <item xmlns="jabber:iq:roster" 625 jid="test@example.org"> 626 <group>Friends</group> 627 <group>Twisted</group> 628 </item> 629 """ 630 631 item = xmppim.RosterItem.fromElement(parseXml(xml)) 632 self.assertIn(u"Twisted", item.groups) 633 self.assertIn(u"Friends", item.groups) 634 635 636 def test_fromElementSubscriptionNone(self): 637 """ 638 Subscription 'none' sets both attributes to False. 639 """ 640 641 xml = """ 642 <item xmlns="jabber:iq:roster" 643 jid="test@example.org" 644 subscription="none"/> 645 """ 646 647 item = xmppim.RosterItem.fromElement(parseXml(xml)) 648 self.assertFalse(item.remove) 649 self.assertFalse(item.subscriptionTo) 650 self.assertFalse(item.subscriptionFrom) 651 652 653 def test_fromElementSubscriptionTo(self): 654 """ 655 Subscription 'to' sets the corresponding attribute to True. 656 """ 657 658 xml = """ 659 <item xmlns="jabber:iq:roster" 660 jid="test@example.org" 661 subscription="to"/> 662 """ 663 664 item = xmppim.RosterItem.fromElement(parseXml(xml)) 665 self.assertFalse(item.remove) 666 self.assertTrue(item.subscriptionTo) 667 self.assertFalse(item.subscriptionFrom) 668 669 670 def test_fromElementSubscriptionFrom(self): 671 """ 672 Subscription 'from' sets the corresponding attribute to True. 673 """ 674 675 xml = """ 676 <item xmlns="jabber:iq:roster" 677 jid="test@example.org" 678 subscription="from"/> 679 """ 680 681 item = xmppim.RosterItem.fromElement(parseXml(xml)) 682 self.assertFalse(item.remove) 683 self.assertFalse(item.subscriptionTo) 684 self.assertTrue(item.subscriptionFrom) 685 686 687 def test_fromElementSubscriptionBoth(self): 688 """ 689 Subscription 'both' sets both attributes to True. 690 """ 691 692 xml = """ 693 <item xmlns="jabber:iq:roster" 694 jid="test@example.org" 695 subscription="both"/> 696 """ 697 698 item = xmppim.RosterItem.fromElement(parseXml(xml)) 699 self.assertFalse(item.remove) 700 self.assertTrue(item.subscriptionTo) 701 self.assertTrue(item.subscriptionFrom) 702 703 704 def test_fromElementSubscriptionRemove(self): 705 """ 706 Subscription 'remove' sets the remove attribute. 707 """ 708 709 xml = """ 710 <item xmlns="jabber:iq:roster" 711 jid="test@example.org" 712 subscription="remove"/> 713 """ 714 715 item = xmppim.RosterItem.fromElement(parseXml(xml)) 716 self.assertTrue(item.remove) 717 718 719 def test_fromElementPendingOut(self): 720 """ 721 The ask attribute, if set to 'subscription', means pending out. 722 """ 723 724 xml = """ 725 <item xmlns="jabber:iq:roster" 726 jid="test@example.org" 727 ask="subscribe"/> 728 """ 729 730 item = xmppim.RosterItem.fromElement(parseXml(xml)) 731 self.assertTrue(item.pendingOut) 732 733 734 def test_fromElementApprovedTrue(self): 735 """ 736 The approved attribute (true) signals a pre-approved subscription. 737 """ 738 739 xml = """ 740 <item xmlns="jabber:iq:roster" 741 jid="test@example.org" 742 approved="true"/> 743 """ 744 745 item = xmppim.RosterItem.fromElement(parseXml(xml)) 746 self.assertTrue(item.approved) 747 748 749 def test_fromElementApproved1(self): 750 """ 751 The approved attribute (1) signals a pre-approved subscription. 752 """ 753 754 xml = """ 755 <item xmlns="jabber:iq:roster" 756 jid="test@example.org" 757 approved="1"/> 758 """ 759 760 item = xmppim.RosterItem.fromElement(parseXml(xml)) 761 self.assertTrue(item.approved) 762 763 764 def test_jidDeprecationGet(self): 765 """ 766 Getting the jid attribute works as entity and warns deprecation. 767 """ 768 item = xmppim.RosterItem(JID('user@example.org')) 769 entity = self.assertWarns(DeprecationWarning, 770 "wokkel.xmppim.RosterItem.jid is deprecated. " 771 "Use RosterItem.entity instead.", 772 xmppim.__file__, 773 getattr, item, 'jid') 774 self.assertIdentical(entity, item.entity) 775 776 777 def test_jidDeprecationSet(self): 778 """ 779 Setting the jid attribute works as entity and warns deprecation. 780 """ 781 item = xmppim.RosterItem(JID('user@example.org')) 782 self.assertWarns(DeprecationWarning, 783 "wokkel.xmppim.RosterItem.jid is deprecated. " 784 "Use RosterItem.entity instead.", 785 xmppim.__file__, 786 setattr, item, 'jid', 787 JID('other@example.org')) 788 self.assertEqual(JID('other@example.org'), item.entity) 789 790 791 def test_askDeprecationGet(self): 792 """ 793 Getting the ask attribute works as entity and warns deprecation. 794 """ 795 item = xmppim.RosterItem(JID('user@example.org')) 796 item.pendingOut = True 797 ask = self.assertWarns(DeprecationWarning, 798 "wokkel.xmppim.RosterItem.ask is deprecated. " 799 "Use RosterItem.pendingOut instead.", 800 xmppim.__file__, 801 getattr, item, 'ask') 802 self.assertTrue(ask) 803 804 805 def test_askDeprecationSet(self): 806 """ 807 Setting the ask attribute works as entity and warns deprecation. 808 """ 809 item = xmppim.RosterItem(JID('user@example.org')) 810 self.assertWarns(DeprecationWarning, 811 "wokkel.xmppim.RosterItem.ask is deprecated. " 812 "Use RosterItem.pendingOut instead.", 813 xmppim.__file__, 814 setattr, item, 'ask', 815 True) 816 self.assertTrue(item.pendingOut) 817 818 819 820 class RosterRequestTest(unittest.TestCase): 821 """ 822 Tests for L{xmppim.RosterRequest}. 823 """ 824 825 def test_fromElement(self): 826 """ 827 A bare roster request is parsed and missing information is None. 828 """ 829 xml = """ 830 <iq type='get' to='this@example.org/Home' from='this@example.org'> 831 <query xmlns='jabber:iq:roster'/> 832 </iq> 833 """ 834 835 request = xmppim.RosterRequest.fromElement(parseXml(xml)) 836 self.assertEqual('get', request.stanzaType) 837 self.assertEqual(JID('this@example.org/Home'), request.recipient) 838 self.assertEqual(JID('this@example.org'), request.sender) 839 self.assertEqual(None, request.item) 840 841 842 def test_fromElementItem(self): 843 """ 844 If an item is present, parse it and put it in the request item. 845 """ 846 xml = """ 847 <iq type='set' to='this@example.org/Home' from='this@example.org'> 848 <query xmlns='jabber:iq:roster'> 849 <item jid='user@example.org'/> 850 </query> 851 </iq> 852 """ 853 854 request = xmppim.RosterRequest.fromElement(parseXml(xml)) 855 self.assertNotIdentical(None, request.item) 856 self.assertEqual(JID('user@example.org'), request.item.entity) 857 858 859 def test_toElement(self): 860 """ 861 A roster request has a query element in the roster namespace. 862 """ 863 request = xmppim.RosterRequest() 864 element = request.toElement() 865 children = element.elements() 866 child = children.next() 867 self.assertEqual(NS_ROSTER, child.uri) 868 self.assertEqual('query', child.name) 869 870 871 def test_toElementItem(self): 872 """ 873 If an item is set, it is rendered as a child of the query. 874 """ 875 request = xmppim.RosterRequest() 876 request.item = xmppim.RosterItem(JID('user@example.org')) 877 element = request.toElement() 878 children = element.query.elements() 879 child = children.next() 880 self.assertEqual(NS_ROSTER, child.uri) 881 self.assertEqual('item', child.name) 882 883 884 885 class FakeClient(object): 886 """ 887 Fake client stream manager for roster tests. 888 """ 889 890 def __init__(self, xmlstream, jid): 891 self.xmlstream = xmlstream 892 self.jid = jid 893 894 895 def request(self, request): 896 element = request.toElement() 897 self.xmlstream.send(element) 898 return defer.Deferred() 899 900 901 def addHandler(self, handler): 902 handler.makeConnection(self.xmlstream) 903 handler.connectionInitialized() 904 905 906 907 class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin): 453 908 """ 454 909 Tests for L{xmppim.RosterClientProtocol}. … … 457 912 def setUp(self): 458 913 self.stub = XmlStreamStub() 459 self. protocol = xmppim.RosterClientProtocol()460 self. protocol.xmlstream = self.stub.xmlstream461 self. protocol.connectionInitialized()914 self.client = FakeClient(self.stub.xmlstream, JID('this@example.org')) 915 self.service = xmppim.RosterClientProtocol() 916 self.service.setHandlerParent(self.client) 462 917 463 918 … … 466 921 Removing a roster item is setting an item with subscription C{remove}. 467 922 """ 468 d = self. protocol.removeItem(JID('test@example.org'))923 d = self.service.removeItem(JID('test@example.org')) 469 924 470 925 # Inspect outgoing iq request 471 926 472 927 iq = self.stub.output[-1] 473 self.assertEqual s('set', iq.getAttribute('type'))928 self.assertEqual('set', iq.getAttribute('type')) 474 929 self.assertNotIdentical(None, iq.query) 475 self.assertEqual s(NS_ROSTER, iq.query.uri)930 self.assertEqual(NS_ROSTER, iq.query.uri) 476 931 477 932 children = list(domish.generateElementsQNamed(iq.query.children, 478 933 'item', NS_ROSTER)) 479 self.assertEqual s(1, len(children))934 self.assertEqual(1, len(children)) 480 935 child = children[0] 481 self.assertEqual s('test@example.org', child['jid'])482 self.assertEqual s('remove', child['subscription'])936 self.assertEqual('test@example.org', child['jid']) 937 self.assertEqual('remove', child.getAttribute('subscription')) 483 938 484 939 # Fake successful response 485 940 486 941 response = toResponse(iq, 'result') 487 self.stub.send(response) 488 return d 942 d.callback(response) 943 return d 944 945 946 def test_getRoster(self): 947 """ 948 A request for the roster is sent out and the response is parsed. 949 """ 950 def cb(roster): 951 self.assertIn(JID('user@example.org'), roster) 952 953 d = self.service.getRoster() 954 d.addCallback(cb) 955 956 # Inspect outgoing iq request 957 958 iq = self.stub.output[-1] 959 self.assertEqual('get', iq.getAttribute('type')) 960 self.assertNotIdentical(None, iq.query) 961 self.assertEqual(NS_ROSTER, iq.query.uri) 962 963 # Fake successful response 964 response = toResponse(iq, 'result') 965 query = response.addElement((NS_ROSTER, 'query')) 966 item = query.addElement('item') 967 item['jid'] = 'user@example.org' 968 969 d.callback(response) 970 return d 971 972 973 def test_onRosterSet(self): 974 """ 975 A roster push causes onRosterSet to be called with the parsed item. 976 """ 977 xml = """ 978 <iq type='set'> 979 <query xmlns='jabber:iq:roster'> 980 <item jid='user@example.org'/> 981 </query> 982 </iq> 983 """ 984 985 items = [] 986 987 def onRosterSet(item): 988 items.append(item) 989 990 def cb(result): 991 self.assertEqual(1, len(items)) 992 self.assertEqual(JID('user@example.org'), items[0].entity) 993 994 self.service.onRosterSet = onRosterSet 995 996 d = self.assertWarns(DeprecationWarning, 997 "wokkel.xmppim.RosterClientProtocol.onRosterSet " 998 "is deprecated. " 999 "Use RosterClientProtocol.setReceived instead.", 1000 xmppim.__file__, 1001 self.handleRequest, xml) 1002 d.addCallback(cb) 1003 return d 1004 1005 1006 def test_onRosterRemove(self): 1007 """ 1008 A roster push causes onRosterSet to be called with the parsed item. 1009 """ 1010 xml = """ 1011 <iq type='set'> 1012 <query xmlns='jabber:iq:roster'> 1013 <item jid='user@example.org' subscription='remove'/> 1014 </query> 1015 </iq> 1016 """ 1017 1018 entities = [] 1019 1020 def onRosterRemove(entity): 1021 entities.append(entity) 1022 1023 def cb(result): 1024 self.assertEqual([JID('user@example.org')], entities) 1025 1026 self.service.onRosterRemove = onRosterRemove 1027 1028 d = self.assertWarns(DeprecationWarning, 1029 "wokkel.xmppim.RosterClientProtocol.onRosterRemove " 1030 "is deprecated. " 1031 "Use RosterClientProtocol.removeReceived instead.", 1032 xmppim.__file__, 1033 self.handleRequest, xml) 1034 d.addCallback(cb) 1035 return d 1036 1037 1038 def test_setReceived(self): 1039 """ 1040 A roster set push causes setReceived. 1041 """ 1042 xml = """ 1043 <iq type='set'> 1044 <query xmlns='jabber:iq:roster'> 1045 <item jid='user@example.org'/> 1046 </query> 1047 </iq> 1048 """ 1049 1050 requests = [] 1051 1052 def setReceived(request): 1053 requests.append(request) 1054 1055 def cb(result): 1056 self.assertEqual(1, len(requests), "setReceived was not called") 1057 self.assertEqual(JID('user@example.org'), requests[0].item.entity) 1058 1059 self.service.setReceived = setReceived 1060 1061 d = self.handleRequest(xml) 1062 d.addCallback(cb) 1063 return d 1064 1065 1066 def test_setReceivedOtherSource(self): 1067 """ 1068 Roster pushes can be sent from other entities, too, ignore them. 1069 """ 1070 xml = """ 1071 <iq type='set' to='this@example.org/Home' from='other@example.org'> 1072 <query xmlns='jabber:iq:roster'> 1073 <item jid='user@example.org'/> 1074 </query> 1075 </iq> 1076 """ 1077 1078 def cb(result): 1079 self.assertEquals('service-unavailable', result.condition) 1080 1081 d = self.handleRequest(xml) 1082 self.assertFailure(d, error.StanzaError) 1083 d.addCallback(cb) 1084 return d 1085 1086 1087 def test_setReceivedOtherSourceAllowed(self): 1088 """ 1089 Roster pushes can be sent from other entities, allow them. 1090 """ 1091 xml = """ 1092 <iq type='set' to='this@example.org/Home' from='other@example.org'> 1093 <query xmlns='jabber:iq:roster'> 1094 <item jid='user@example.org'/> 1095 </query> 1096 </iq> 1097 """ 1098 1099 self.service.allowAnySender = True 1100 requests = [] 1101 1102 def setReceived(request): 1103 requests.append(request) 1104 1105 def cb(result): 1106 self.assertEqual(1, len(requests), "setReceived was not called") 1107 1108 self.service.setReceived = setReceived 1109 1110 d = self.handleRequest(xml) 1111 d.addCallback(cb) 1112 return d 1113 1114 1115 def test_setReceivedOtherSourceIgnored(self): 1116 """ 1117 Roster pushes can be sent from other entities, allow them. 1118 """ 1119 xml = """ 1120 <iq type='set' to='this@example.org/Home' from='bad@example.org'> 1121 <query xmlns='jabber:iq:roster'> 1122 <item jid='user@example.org'/> 1123 </query> 1124 </iq> 1125 """ 1126 1127 self.service.allowAnySender = True 1128 1129 def setReceived(request): 1130 if request.sender == JID('bad@example.org'): 1131 raise xmppim.RosterPushIgnored() 1132 1133 def cb(result): 1134 self.assertEquals('service-unavailable', result.condition) 1135 1136 self.service.setReceived = setReceived 1137 1138 1139 d = self.handleRequest(xml) 1140 self.assertFailure(d, error.StanzaError) 1141 d.addCallback(cb) 1142 return d 1143 1144 1145 def test_removeReceived(self): 1146 """ 1147 A roster remove push causes removeReceived. 1148 """ 1149 xml = """ 1150 <iq type='set'> 1151 <query xmlns='jabber:iq:roster'> 1152 <item jid='user@example.org' subscription='remove'/> 1153 </query> 1154 </iq> 1155 """ 1156 1157 requests = [] 1158 1159 def removeReceived(request): 1160 requests.append(request) 1161 1162 def cb(result): 1163 self.assertEqual(1, len(requests), "removeReceived was not called") 1164 self.assertEqual(JID('user@example.org'), requests[0].item.entity) 1165 1166 self.service.removeReceived = removeReceived 1167 1168 d = self.handleRequest(xml) 1169 d.addCallback(cb) 1170 return d -
wokkel/xmppim.py
r166 r172 8 8 9 9 This module provides generic implementations for the protocols defined in 10 U{RFC 3921<http://xmpp.org/rfcs/rfc3921.html>} (XMPP IM). 11 12 All of it should eventually move to Twisted. 10 U{RFC 6121<http://www.xmpp.org/rfcs/rfc6121.html>} (XMPP IM). 13 11 """ 14 12 13 import warnings 14 15 from twisted.internet import defer 16 from twisted.words.protocols.jabber import error 15 17 from twisted.words.protocols.jabber.jid import JID 16 18 from twisted.words.xish import domish 17 19 18 from wokkel. compat import IQ19 from wokkel. generic import ErrorStanza, Stanza20 from wokkel.generic import ErrorStanza, Stanza, Request 21 from wokkel.subprotocols import IQHandlerMixin 20 22 from wokkel.subprotocols import XMPPHandler 21 23 22 24 NS_XML = 'http://www.w3.org/XML/1998/namespace' 23 25 NS_ROSTER = 'jabber:iq:roster' 26 27 XPATH_ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER 28 29 24 30 25 31 class Presence(domish.Element): … … 606 612 This represents one contact from an XMPP contact list known as roster. 607 613 608 @ivar jid: The JID of the contact.609 @type jid: L{JID}610 @ivar name: The optionalassociated nickname for this contact.614 @ivar entity: The JID of the contact. 615 @type entity: L{JID} 616 @ivar name: The associated nickname for this contact. 611 617 @type name: C{unicode} 612 618 @ivar subscriptionTo: Subscription state to contact's presence. If C{True}, … … 617 623 contact is subscribed to the presence information 618 624 of the roster owner. 619 @type subscriptionTo: C{bool} 620 @ivar ask: Whether subscription is pending. 621 @type ask: C{bool} 625 @type subscriptionFrom: C{bool} 626 @ivar pendingOut: Whether the subscription request to this contact is 627 pending. 628 @type pendingOut: C{bool} 622 629 @ivar groups: Set of groups this contact is categorized in. Groups are 623 630 represented by an opaque identifier of type C{unicode}. 624 631 @type groups: C{set} 625 """ 626 627 def __init__(self, jid): 628 self.jid = jid 629 self.name = None 630 self.subscriptionTo = False 631 self.subscriptionFrom = False 632 self.ask = None 633 self.groups = set() 634 635 636 class RosterClientProtocol(XMPPHandler): 632 @ivar approved: Signals pre-approved subscription. 633 @type approved: C{bool} 634 @ivar remove: Signals roster item removal. 635 @type remove: C{bool} 636 """ 637 638 __subscriptionStates = {(False, False): None, 639 (True, False): 'to', 640 (False, True): 'from', 641 (True, True): 'both'} 642 643 def __init__(self, entity, subscriptionTo=False, subscriptionFrom=False, 644 name=u'', groups=None): 645 self.entity = entity 646 self.subscriptionTo = subscriptionTo 647 self.subscriptionFrom = subscriptionFrom 648 self.name = name 649 self.groups = groups or set() 650 651 self.pendingOut = False 652 self.approved = False 653 self.remove = False 654 655 656 def __getJID(self): 657 warnings.warn( 658 "wokkel.xmppim.RosterItem.jid is deprecated. " 659 "Use RosterItem.entity instead.", 660 DeprecationWarning) 661 return self.entity 662 663 664 def __setJID(self, value): 665 warnings.warn( 666 "wokkel.xmppim.RosterItem.jid is deprecated. " 667 "Use RosterItem.entity instead.", 668 DeprecationWarning) 669 self.entity = value 670 671 672 jid = property(__getJID, __setJID, doc=""" 673 JID of the contact. Deprecated in favour of C{entity}.""") 674 675 676 def __getAsk(self): 677 warnings.warn( 678 "wokkel.xmppim.RosterItem.ask is deprecated. " 679 "Use RosterItem.pendingOut instead.", 680 DeprecationWarning) 681 return self.pendingOut 682 683 684 def __setAsk(self, value): 685 warnings.warn( 686 "wokkel.xmppim.RosterItem.ask is deprecated. " 687 "Use RosterItem.pendingOut instead.", 688 DeprecationWarning) 689 self.pendingOut = value 690 691 692 ask = property(__getAsk, __setAsk, doc=""" 693 Pending out subscription. Deprecated in favour of C{pendingOut}.""") 694 695 696 def toElement(self): 697 element = domish.Element((NS_ROSTER, 'item')) 698 element['jid'] = self.entity.full() 699 700 if self.remove: 701 subscription = 'remove' 702 else: 703 subscription = self.__subscriptionStates[self.subscriptionTo, 704 self.subscriptionFrom] 705 706 if self.pendingOut: 707 element['ask'] = u'subscribe' 708 709 if self.name: 710 element['name'] = self.name 711 712 if self.approved: 713 element['approved'] = u'true' 714 715 if self.groups: 716 for group in self.groups: 717 element.addElement('group', content=group) 718 719 if subscription: 720 element['subscription'] = subscription 721 722 return element 723 724 725 @classmethod 726 def fromElement(Class, element): 727 entity = JID(element['jid']) 728 item = Class(entity) 729 subscription = element.getAttribute('subscription') 730 if subscription == 'remove': 731 item.remove = True 732 else: 733 item.name = element.getAttribute('name', u'') 734 item.subscriptionTo = subscription in ('to', 'both') 735 item.subscriptionFrom = subscription in ('from', 'both') 736 item.pendingOut = element.getAttribute('ask') == 'subscribe' 737 item.approved = element.getAttribute('approved') in ('true', '1') 738 for subElement in domish.generateElementsQNamed(element.children, 739 'group', NS_ROSTER): 740 item.groups.add(unicode(subElement)) 741 return item 742 743 744 745 class RosterRequest(Request): 746 """ 747 Roster request. 748 749 @ivar item: Roster item to be set or pushed. 750 @type item: L{RosterItem}. 751 """ 752 item = None 753 754 def parseRequest(self, element): 755 for child in element.elements(NS_ROSTER, 'item'): 756 self.item = RosterItem.fromElement(child) 757 break 758 759 760 def toElement(self): 761 element = Request.toElement(self) 762 query = element.addElement((NS_ROSTER, 'query')) 763 if self.item: 764 query.addChild(self.item.toElement()) 765 return element 766 767 768 769 class RosterPushIgnored(Exception): 770 """ 771 Raised when this entity doesn't want to accept/trust a roster push. 772 773 To avert presence leaks, a handler can raise L{RosterPushIgnored} when 774 not accepting a roster push (directly or via Deferred). This will 775 result in a C{'service-unavailable'} error being sent in return. 776 """ 777 778 779 780 class RosterClientProtocol(XMPPHandler, IQHandlerMixin): 637 781 """ 638 782 Client side XMPP roster protocol. 639 """ 783 784 The roster can be retrieved using L{getRoster}. Subsequent changes to the 785 roster will be pushed, resulting in calls to L{setReceived} or 786 L{removeReceived}. These methods should be overridden to handle the 787 roster pushes. 788 789 RFC 6121 specifically allows entities other than a user's server to 790 hold a roster for that user. However, how a client should deal with 791 that is currently not yet specfied. 792 793 By default roster pushes from other source. I.e. when C{request.sender} 794 is set but the sender's bare JID is different from the user's bare JID. 795 Set L{allowAnySender} to allow roster pushes from any sender. To 796 avert presence leaks, L{RosterPushIgnored} should then be raised for 797 pushes from untrusted senders. 798 799 @cvar allowAnySender: Flag to allow roster pushes from any sender. 800 C{False} by default. 801 @type allowAnySender: C{boolean} 802 """ 803 804 allowAnySender = False 805 iqHandlers = {XPATH_ROSTER_SET: "_onRosterSet"} 806 640 807 641 808 def connectionInitialized(self): 642 ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER 643 self.xmlstream.addObserver(ROSTER_SET, self._onRosterSet) 644 645 def _parseRosterItem(self, element): 646 jid = JID(element['jid']) 647 item = RosterItem(jid) 648 item.name = element.getAttribute('name') 649 subscription = element.getAttribute('subscription') 650 item.subscriptionTo = subscription in ('to', 'both') 651 item.subscriptionFrom = subscription in ('from', 'both') 652 item.ask = element.getAttribute('ask') == 'subscribe' 653 for subElement in domish.generateElementsQNamed(element.children, 654 'group', NS_ROSTER): 655 item.groups.add(unicode(subElement)) 656 657 return item 809 self.xmlstream.addObserver(XPATH_ROSTER_SET, self.handleRequest) 810 658 811 659 812 def getRoster(self): … … 669 822 for element in domish.generateElementsQNamed(result.query.children, 670 823 'item', NS_ROSTER): 671 item = self._parseRosterItem(element)672 roster[item. jid.userhost()] = item824 item = RosterItem.fromElement(element) 825 roster[item.entity] = item 673 826 674 827 return roster 675 828 676 iq = IQ(self.xmlstream, 'get') 677 iq.addElement((NS_ROSTER, 'query')) 678 d = iq.send() 829 request = RosterRequest(stanzaType='get') 830 d = self.request(request) 679 831 d.addCallback(processRoster) 680 832 return d … … 689 841 @rtype: L{twisted.internet.defer.Deferred} 690 842 """ 691 iq = IQ(self.xmlstream, 'set') 692 iq.addElement((NS_ROSTER, 'query')) 693 item = iq.query.addElement('item') 694 item['jid'] = entity.full() 695 item['subscription'] = 'remove' 696 return iq.send() 843 request = RosterRequest(stanzaType='set') 844 request.item = RosterItem(entity) 845 request.item.remove = True 846 return self.request(request) 697 847 698 848 699 849 def _onRosterSet(self, iq): 700 if iq.handled or \ 701 iq.hasAttribute('from') and iq['from'] != self.xmlstream: 702 return 703 704 iq.handled = True 705 706 itemElement = iq.query.item 707 708 if unicode(itemElement['subscription']) == 'remove': 709 self.onRosterRemove(JID(itemElement['jid'])) 850 def trapIgnored(failure): 851 failure.trap(RosterPushIgnored) 852 raise error.StanzaError('service-unavailable') 853 854 request = RosterRequest.fromElement(iq) 855 856 if (not self.allowAnySender and 857 request.sender and 858 request.sender.userhostJID() != 859 self.parent.jid.userhostJID()): 860 d = defer.fail(RosterPushIgnored()) 861 elif request.item.remove: 862 d = defer.maybeDeferred(self.removeReceived, request) 710 863 else: 711 item = self._parseRosterItem(iq.query.item) 712 self.onRosterSet(item) 713 714 def onRosterSet(self, item): 864 d = defer.maybeDeferred(self.setReceived, request) 865 d.addErrback(trapIgnored) 866 return d 867 868 869 def setReceived(self, request): 715 870 """ 716 871 Called when a roster push for a new or update item was received. 717 872 718 @param item: The pushed roster item. 719 @type item: L{RosterItem} 720 """ 721 722 def onRosterRemove(self, entity): 873 @param request: The push request. 874 @type request: L{RosterRequest} 875 """ 876 if hasattr(self, 'onRosterSet'): 877 warnings.warn( 878 "wokkel.xmppim.RosterClientProtocol.onRosterSet " 879 "is deprecated. " 880 "Use RosterClientProtocol.setReceived instead.", 881 DeprecationWarning) 882 return defer.maybeDeferred(self.onRosterSet, request.item) 883 884 885 def removeReceived(self, request): 723 886 """ 724 887 Called when a roster push for the removal of an item was received. 725 888 726 @param entity: The entity for which the roster item has been removed. 727 @type entity: L{JID} 728 """ 889 @param request: The push request. 890 @type request: L{RosterRequest} 891 """ 892 if hasattr(self, 'onRosterRemove'): 893 warnings.warn( 894 "wokkel.xmppim.RosterClientProtocol.onRosterRemove " 895 "is deprecated. " 896 "Use RosterClientProtocol.removeReceived instead.", 897 DeprecationWarning) 898 return defer.maybeDeferred(self.onRosterRemove, 899 request.item.entity) 729 900 730 901
Note: See TracChangeset
for help on using the changeset viewer.