source:
ralphm-patches/c2s_server_factory.patch
Last change on this file was 80:80ed2848c4e0, checked in by Ralph Meijer <ralphm@…>, 5 years ago | |
---|---|
File size: 12.0 KB |
-
wokkel/client.py
# HG changeset patch # Parent 49294b2cf829414b42141731b5130d91474c0443 # Parent 0a68c01ed5b5f429eed343df521bc706fe88afd1 Add factory for accepting client connections. The new `XMPPC2SServerFactory` is a server factory for accepting client connections. It uses `XMPPClientListenAuthenticator` to perform the steps for authentication and binding of a resource. For each connection, the factory also sets up subprotocol handlers by calling `setupHandlers`. By default these are `RecipientAddressStamper` and `StanzaForwarder`. The former makes sure that all XML stanzas received from the client are stamped with a proper recipient address. The latter passes stanzas on to the stream's avatar. TODO: * Add tests. diff --git a/wokkel/client.py b/wokkel/client.py
a b 22 22 23 23 from wokkel import generic 24 24 from wokkel.iwokkel import IUserSession 25 from wokkel.subprotocols import ServerStreamManager 25 26 from wokkel.subprotocols import StreamManager 27 from wokkel.subprotocols import XMPPHandler 26 28 27 29 NS_CLIENT = 'jabber:client' 28 30 … … 480 482 self.portal = self.portals[self.xmlstream.thisEntity] 481 483 except KeyError: 482 484 raise error.StreamError('host-unknown') 485 486 487 488 class RecipientAddressStamper(XMPPHandler): 489 """ 490 Protocol handler to ensure client stanzas have a sender address. 491 """ 492 493 def connectionInitialized(self): 494 self.xmlstream.addObserver('/*', self.onStanza, priority=1) 495 496 497 def onStanza(self, element): 498 """ 499 Make sure each stanza has a sender address. 500 """ 501 if element.uri != self.xmlstream.namespace: 502 return 503 504 if (element.name == 'presence' and 505 element.getAttribute('type') in ('subscribe', 'subscribed', 506 'unsubscribe', 'unsubscribed')): 507 element['from'] = self.xmlstream.avatar.entity.userhost() 508 elif element.name in ('message', 'presence', 'iq'): 509 element['from'] = self.xmlstream.avatar.entity.full() 510 511 512 513 class XMPPC2SServerFactory(xmlstream.XmlStreamServerFactory): 514 """ 515 Server factory for XMPP client-server connections. 516 """ 517 518 def __init__(self, portals): 519 def authenticatorFactory(): 520 return XMPPClientListenAuthenticator(portals) 521 522 xmlstream.XmlStreamServerFactory.__init__(self, authenticatorFactory) 523 self.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, 524 self.onConnectionMade) 525 526 527 def onConnectionMade(self, xs): 528 """ 529 Called when a connection is made. 530 531 This creates a stream manager, calls L{setupHandlers} to attach 532 subprotocol handlers and then signals the stream manager that 533 the connection was made. 534 """ 535 sm = ServerStreamManager() 536 sm.logTraffic = self.logTraffic 537 538 for handler in self.setupHandlers(): 539 handler.setHandlerParent(sm) 540 541 sm.makeConnection(xs) 542 543 544 def setupHandlers(self): 545 """ 546 Set up XMPP subprotocol handlers. 547 """ 548 return [ 549 generic.StanzaForwarder(), 550 RecipientAddressStamper(), 551 ] -
wokkel/generic.py
diff --git a/wokkel/generic.py b/wokkel/generic.py
a b 480 480 standard full stop. 481 481 """ 482 482 return name.encode('idna') 483 484 485 486 class StanzaForwarder(XMPPHandler): 487 """ 488 XMPP protocol for passing incoming stanzas to the stream avatar. 489 490 This handler adds an observer for all XML Stanzas to forward to the C{send} 491 method on the cred avatar set on the XML Stream, unless it has been handled 492 by other observers. 493 494 Stream errors are logged. 495 """ 496 497 def connectionMade(self): 498 """ 499 Called when a connection is made. 500 """ 501 self.xmlstream.addObserver(xmlstream.STREAM_ERROR_EVENT, self.onError) 502 503 504 def connectionInitialized(self): 505 """ 506 Called when the stream has been initialized. 507 """ 508 self.xmlstream.addObserver('/*', self.onStanza, priority=-1) 509 510 511 512 def onStanza(self, element): 513 """ 514 Called when a stanza element was received. 515 516 If this is an XML stanza, and it has not been handled by another 517 subprotocol handler, the stanza is passed on to the avatar's C{send} 518 method. 519 520 If there is no recipient address on the stanza, a service-unavailable 521 is returned instead. 522 """ 523 if element.handled: 524 return 525 526 if (element.name not in ('iq', 'message', 'presence') or 527 element.uri != self.xmlstream.namespace): 528 return 529 530 stanza = Stanza.fromElement(element) 531 532 if not stanza.recipient: 533 exc = error.StanzaError('service-unavailable') 534 self.send(exc.toResponse(stanza.element)) 535 else: 536 self.xmlstream.avatar.send(stanza.element) 537 538 539 def onError(self, reason): 540 """ 541 Log a stream error. 542 """ 543 log.err(reason, "Stream error") -
wokkel/test/test_client.py
diff --git a/wokkel/test/test_client.py b/wokkel/test/test_client.py
a b 7 7 8 8 from base64 import b64encode 9 9 10 from zope.interface import implement s10 from zope.interface import implementer 11 11 12 12 from twisted.cred.portal import IRealm, Portal 13 13 from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse … … 28 28 29 29 from wokkel import client, iwokkel 30 30 from wokkel.generic import TestableXmlStream, FeatureListenAuthenticator 31 from wokkel.generic import parseXml 32 from wokkel.test.helpers import XmlStreamStub 31 33 32 34 class XMPPClientTest(unittest.TestCase): 33 35 """ … … 180 182 181 183 182 184 185 @implementer(iwokkel.IUserSession) 183 186 class TestSession(object): 184 implements(iwokkel.IUserSession)185 187 186 188 def __init__(self, domain, user): 187 189 self.domain = domain … … 189 191 190 192 191 193 def bindResource(self, resource): 192 return defer.succeed(JID(tuple=(self.user, self.domain, resource))) 194 self.entity = JID(tuple=(self.user, self.domain, resource)) 195 return defer.succeed(self.entity) 193 196 194 197 195 198 199 @implementer(IRealm) 196 200 class TestRealm(object): 197 201 198 implements(IRealm)199 200 202 logoutCalled = False 201 203 202 204 def __init__(self, domain): … … 679 681 "to='example.com' " 680 682 "version='1.0'>") 681 683 self.xmlstream.assertStreamError(self, condition='host-unknown') 684 685 686 687 class RecipientAddressStamperTest(unittest.TestCase): 688 """ 689 Tests for L{client.RecipientAddressStamper}. 690 """ 691 692 693 def setUp(self): 694 self.stub = XmlStreamStub() 695 self.stub.xmlstream.namespace = '' 696 avatar = TestSession(u'example.org', u'test') 697 avatar.bindResource(u'Home') 698 self.stub.xmlstream.avatar = avatar 699 700 self.protocol = client.RecipientAddressStamper() 701 self.protocol.makeConnection(self.stub.xmlstream) 702 self.protocol.connectionInitialized() 703 704 705 def test_presence(self): 706 """ 707 The from address is set to the full JID on presence stanzas. 708 """ 709 xml = """<presence/>""" 710 element = parseXml(xml) 711 self.stub.xmlstream.dispatch(element) 712 self.assertEqual(u'test@example.org/Home', 713 element.getAttribute('from')) 714 715 716 def test_presenceSubscribe(self): 717 """ 718 The from address is set to the bare JID on presence subscribe. 719 """ 720 xml = """<presence type='subscribe'/>""" 721 element = parseXml(xml) 722 self.stub.xmlstream.dispatch(element) 723 self.assertEqual(u'test@example.org', 724 element.getAttribute('from')) 725 726 727 def test_fromAlreadySet(self): 728 """ 729 The from address is overridden if already present. 730 """ 731 xml = """<presence from='test@example.org/Work'/>""" 732 element = parseXml(xml) 733 self.stub.xmlstream.dispatch(element) 734 self.assertEqual(u'test@example.org/Home', 735 element.getAttribute('from')) 736 737 738 def test_notHandled(self): 739 """ 740 The stanza will not have its 'handled' attribute set to True. 741 """ 742 xml = """<presence/>""" 743 element = parseXml(xml) 744 self.stub.xmlstream.dispatch(element) 745 self.assertFalse(element.handled) 746 747 748 def test_message(self): 749 """ 750 The from address is set to the full JID on message stanzas. 751 """ 752 xml = """<message to='other@example.org'> 753 <body>Hi!</body> 754 </message>""" 755 element = parseXml(xml) 756 self.stub.xmlstream.dispatch(element) 757 self.assertEqual(u'test@example.org/Home', 758 element.getAttribute('from')) 759 760 761 def test_iq(self): 762 """ 763 The from address is set to the full JID on iq stanzas. 764 """ 765 xml = """<iq type='get' id='g_1'> 766 <query xmlns='jabber:iq:version'/> 767 </iq>""" 768 element = parseXml(xml) 769 self.stub.xmlstream.dispatch(element) 770 self.assertEqual(u'test@example.org/Home', 771 element.getAttribute('from')) -
wokkel/test/test_generic.py
diff --git a/wokkel/test/test_generic.py b/wokkel/test/test_generic.py
a b 728 728 name = u"example.com." 729 729 result = generic.prepareIDNName(name) 730 730 self.assertEqual(b"example.com.", result) 731 732 733 734 class StanzaForwarderTest(unittest.TestCase): 735 """ 736 Tests for L{generic.StanzaForwarder}. 737 """ 738 739 def setUp(self): 740 class Avatar(object): 741 def __init__(self): 742 self.sent = [] 743 744 def send(self, element): 745 self.sent.append(element) 746 747 self.stub = XmlStreamStub() 748 self.avatar = Avatar() 749 self.protocol = generic.StanzaForwarder() 750 self.protocol.makeConnection(self.stub.xmlstream) 751 self.protocol.xmlstream.avatar = self.avatar 752 self.protocol.xmlstream.namespace = u'jabber:client' 753 self.protocol.send = self.protocol.xmlstream.send 754 755 756 def test_onStanza(self): 757 """ 758 An XML stanza is delivered at the stream avatar. 759 """ 760 self.protocol.connectionInitialized() 761 762 element = domish.Element((u'jabber:client', u'message')) 763 element[u'to'] = u'other@example.org' 764 self.stub.send(element) 765 766 self.assertEqual(1, len(self.avatar.sent)) 767 self.assertEqual(0, len(self.stub.output)) 768 769 770 def test_onStanzaNoRecipient(self): 771 """ 772 Stanzas without recipient are rejected. 773 """ 774 self.protocol.connectionInitialized() 775 776 element = domish.Element((u'jabber:client', u'message')) 777 self.stub.send(element) 778 779 self.assertEqual(0, len(self.avatar.sent)) 780 self.assertEqual(1, len(self.stub.output)) 781 782 783 def test_onStanzaWrongNamespace(self): 784 """ 785 If there is no xmlns on the stanza, it should still be delivered. 786 """ 787 self.protocol.connectionInitialized() 788 789 element = domish.Element((u'testns', u'message')) 790 element[u'to'] = u'other@example.org' 791 self.stub.send(element) 792 793 self.assertEqual(0, len(self.avatar.sent)) 794 self.assertEqual(0, len(self.stub.output)) 795 796 797 def test_onStanzaAlreadyHandled(self): 798 """ 799 If the stanza is marked as handled, ignore it. 800 """ 801 self.protocol.connectionInitialized() 802 803 element = domish.Element((None, u'message')) 804 element[u'to'] = u'other@example.org' 805 element.handled = True 806 self.stub.send(element) 807 808 self.assertEqual(0, len(self.avatar.sent)) 809 self.assertEqual(0, len(self.stub.output)) 810 811 812 def test_onError(self): 813 """ 814 A stream error is logged. 815 """ 816 exc = error.StreamError('host-unknown') 817 self.stub.xmlstream.dispatch(exc, xmlstream.STREAM_ERROR_EVENT) 818 self.assertEqual(1, len(self.flushLoggedErrors(error.StreamError)))
Note: See TracBrowser
for help on using the repository browser.