| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.words.test.test_jabbercomponent -*- | |
| 2 # | |
| 3 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
| 4 # See LICENSE for details. | |
| 5 | |
| 6 """ | |
| 7 External server-side components. | |
| 8 | |
| 9 Most Jabber server implementations allow for add-on components that act as a | |
| 10 seperate entity on the Jabber network, but use the server-to-server | |
| 11 functionality of a regular Jabber IM server. These so-called 'external | |
| 12 components' are connected to the Jabber server using the Jabber Component | |
| 13 Protocol as defined in U{JEP-0114<http://www.jabber.org/jeps/jep-0114.html>}. | |
| 14 | |
| 15 This module allows for writing external server-side component by assigning one | |
| 16 or more services implementing L{ijabber.IService} up to L{ServiceManager}. The | |
| 17 ServiceManager connects to the Jabber server and is responsible for the | |
| 18 corresponding XML stream. | |
| 19 """ | |
| 20 | |
| 21 from zope.interface import implements | |
| 22 | |
| 23 from twisted.application import service | |
| 24 from twisted.internet import defer | |
| 25 from twisted.words.xish import domish | |
| 26 from twisted.words.protocols.jabber import ijabber, jstrports, xmlstream | |
| 27 | |
| 28 def componentFactory(componentid, password): | |
| 29 """ | |
| 30 XML stream factory for external server-side components. | |
| 31 | |
| 32 @param componentid: JID of the component. | |
| 33 @type componentid: L{unicode} | |
| 34 @param password: password used to authenticate to the server. | |
| 35 @type password: L{str} | |
| 36 """ | |
| 37 a = ConnectComponentAuthenticator(componentid, password) | |
| 38 return xmlstream.XmlStreamFactory(a) | |
| 39 | |
| 40 class ComponentInitiatingInitializer(object): | |
| 41 """ | |
| 42 External server-side component authentication initializer for the | |
| 43 initiating entity. | |
| 44 | |
| 45 @ivar xmlstream: XML stream between server and component. | |
| 46 @type xmlstream: L{xmlstream.XmlStream} | |
| 47 """ | |
| 48 | |
| 49 def __init__(self, xs): | |
| 50 self.xmlstream = xs | |
| 51 self._deferred = None | |
| 52 | |
| 53 def initialize(self): | |
| 54 xs = self.xmlstream | |
| 55 hs = domish.Element((self.xmlstream.namespace, "handshake")) | |
| 56 hs.addContent(xmlstream.hashPassword(xs.sid, | |
| 57 xs.authenticator.password)) | |
| 58 | |
| 59 # Setup observer to watch for handshake result | |
| 60 xs.addOnetimeObserver("/handshake", self._cbHandshake) | |
| 61 xs.send(hs) | |
| 62 self._deferred = defer.Deferred() | |
| 63 return self._deferred | |
| 64 | |
| 65 def _cbHandshake(self, _): | |
| 66 # we have successfully shaken hands and can now consider this | |
| 67 # entity to represent the component JID. | |
| 68 self.xmlstream.thisEntity = self.xmlstream.otherEntity | |
| 69 self._deferred.callback(None) | |
| 70 | |
| 71 class ConnectComponentAuthenticator(xmlstream.ConnectAuthenticator): | |
| 72 """ | |
| 73 Authenticator to permit an XmlStream to authenticate against a Jabber | |
| 74 server as an external component (where the Authenticator is initiating the | |
| 75 stream). | |
| 76 """ | |
| 77 namespace = 'jabber:component:accept' | |
| 78 | |
| 79 def __init__(self, componentjid, password): | |
| 80 """ | |
| 81 @type componentjid: L{str} | |
| 82 @param componentjid: Jabber ID that this component wishes to bind to. | |
| 83 | |
| 84 @type password: L{str} | |
| 85 @param password: Password/secret this component uses to authenticate. | |
| 86 """ | |
| 87 # Note that we are sending 'to' our desired component JID. | |
| 88 xmlstream.ConnectAuthenticator.__init__(self, componentjid) | |
| 89 self.password = password | |
| 90 | |
| 91 def associateWithStream(self, xs): | |
| 92 xs.version = (0, 0) | |
| 93 xmlstream.ConnectAuthenticator.associateWithStream(self, xs) | |
| 94 | |
| 95 xs.initializers = [ComponentInitiatingInitializer(xs)] | |
| 96 | |
| 97 class ListenComponentAuthenticator(xmlstream.Authenticator): | |
| 98 """ | |
| 99 Placeholder for listening components. | |
| 100 """ | |
| 101 | |
| 102 class Service(service.Service): | |
| 103 """ | |
| 104 External server-side component service. | |
| 105 """ | |
| 106 | |
| 107 implements(ijabber.IService) | |
| 108 | |
| 109 def componentConnected(self, xs): | |
| 110 pass | |
| 111 | |
| 112 def componentDisconnected(self): | |
| 113 pass | |
| 114 | |
| 115 def transportConnected(self, xs): | |
| 116 pass | |
| 117 | |
| 118 def send(self, obj): | |
| 119 """ | |
| 120 Send data over service parent's XML stream. | |
| 121 | |
| 122 @note: L{ServiceManager} maintains a queue for data sent using this | |
| 123 method when there is no current established XML stream. This data is | |
| 124 then sent as soon as a new stream has been established and initialized. | |
| 125 Subsequently, L{componentConnected} will be called again. If this | |
| 126 queueing is not desired, use C{send} on the XmlStream object (passed to | |
| 127 L{componentConnected}) directly. | |
| 128 | |
| 129 @param obj: data to be sent over the XML stream. This is usually an | |
| 130 object providing L{domish.IElement}, or serialized XML. See | |
| 131 L{xmlstream.XmlStream} for details. | |
| 132 """ | |
| 133 | |
| 134 self.parent.send(obj) | |
| 135 | |
| 136 class ServiceManager(service.MultiService): | |
| 137 """ | |
| 138 Business logic representing a managed component connection to a Jabber | |
| 139 router. | |
| 140 | |
| 141 This service maintains a single connection to a Jabber router and provides | |
| 142 facilities for packet routing and transmission. Business logic modules are | |
| 143 services implementing L{ijabber.IService} (like subclasses of L{Service}), a
nd | |
| 144 added as sub-service. | |
| 145 """ | |
| 146 | |
| 147 def __init__(self, jid, password): | |
| 148 service.MultiService.__init__(self) | |
| 149 | |
| 150 # Setup defaults | |
| 151 self.jabberId = jid | |
| 152 self.xmlstream = None | |
| 153 | |
| 154 # Internal buffer of packets | |
| 155 self._packetQueue = [] | |
| 156 | |
| 157 # Setup the xmlstream factory | |
| 158 self._xsFactory = componentFactory(self.jabberId, password) | |
| 159 | |
| 160 # Register some lambda functions to keep the self.xmlstream var up to | |
| 161 # date | |
| 162 self._xsFactory.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT, | |
| 163 self._connected) | |
| 164 self._xsFactory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self._authd) | |
| 165 self._xsFactory.addBootstrap(xmlstream.STREAM_END_EVENT, | |
| 166 self._disconnected) | |
| 167 | |
| 168 # Map addBootstrap and removeBootstrap to the underlying factory -- is | |
| 169 # this right? I have no clue...but it'll work for now, until i can | |
| 170 # think about it more. | |
| 171 self.addBootstrap = self._xsFactory.addBootstrap | |
| 172 self.removeBootstrap = self._xsFactory.removeBootstrap | |
| 173 | |
| 174 def getFactory(self): | |
| 175 return self._xsFactory | |
| 176 | |
| 177 def _connected(self, xs): | |
| 178 self.xmlstream = xs | |
| 179 for c in self: | |
| 180 if ijabber.IService.providedBy(c): | |
| 181 c.transportConnected(xs) | |
| 182 | |
| 183 def _authd(self, xs): | |
| 184 # Flush all pending packets | |
| 185 for p in self._packetQueue: | |
| 186 self.xmlstream.send(p) | |
| 187 self._packetQueue = [] | |
| 188 | |
| 189 # Notify all child services which implement the IService interface | |
| 190 for c in self: | |
| 191 if ijabber.IService.providedBy(c): | |
| 192 c.componentConnected(xs) | |
| 193 | |
| 194 def _disconnected(self, _): | |
| 195 self.xmlstream = None | |
| 196 | |
| 197 # Notify all child services which implement | |
| 198 # the IService interface | |
| 199 for c in self: | |
| 200 if ijabber.IService.providedBy(c): | |
| 201 c.componentDisconnected() | |
| 202 | |
| 203 def send(self, obj): | |
| 204 """ | |
| 205 Send data over the XML stream. | |
| 206 | |
| 207 When there is no established XML stream, the data is queued and sent | |
| 208 out when a new XML stream has been established and initialized. | |
| 209 | |
| 210 @param obj: data to be sent over the XML stream. This is usually an | |
| 211 object providing L{domish.IElement}, or serialized XML. See | |
| 212 L{xmlstream.XmlStream} for details. | |
| 213 """ | |
| 214 | |
| 215 if self.xmlstream != None: | |
| 216 self.xmlstream.send(obj) | |
| 217 else: | |
| 218 self._packetQueue.append(obj) | |
| 219 | |
| 220 def buildServiceManager(jid, password, strport): | |
| 221 """ | |
| 222 Constructs a pre-built L{ServiceManager}, using the specified strport | |
| 223 string. | |
| 224 """ | |
| 225 | |
| 226 svc = ServiceManager(jid, password) | |
| 227 client_svc = jstrports.client(strport, svc.getFactory()) | |
| 228 client_svc.setServiceParent(svc) | |
| 229 return svc | |
| OLD | NEW |