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 |