| 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 | 
|---|