~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/words/protocols/jabber/client.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.words.test.test_jabberclient -*-
 
2
#
 
3
# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
from twisted.internet import defer
 
7
from twisted.words.xish import domish, xpath, utility
 
8
from twisted.words.protocols.jabber import xmlstream, sasl, error
 
9
from twisted.words.protocols.jabber.jid import JID
 
10
 
 
11
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
 
12
NS_XMPP_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
 
13
NS_XMPP_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
 
14
NS_IQ_AUTH_FEATURE = 'http://jabber.org/features/iq-auth'
 
15
 
 
16
DigestAuthQry = xpath.internQuery("/iq/query/digest")
 
17
PlaintextAuthQry = xpath.internQuery("/iq/query/password")
 
18
 
 
19
def basicClientFactory(jid, secret):
 
20
    a = BasicAuthenticator(jid, secret)
 
21
    return xmlstream.XmlStreamFactory(a)
 
22
 
 
23
class IQ(domish.Element):
 
24
    """
 
25
    Wrapper for a Info/Query packet.
 
26
 
 
27
    This provides the necessary functionality to send IQs and get notified when
 
28
    a result comes back. It's a subclass from L{domish.Element}, so you can use
 
29
    the standard DOM manipulation calls to add data to the outbound request.
 
30
 
 
31
    @type callbacks: L{utility.CallbackList}
 
32
    @cvar callbacks: Callback list to be notified when response comes back
 
33
 
 
34
    """
 
35
    def __init__(self, xmlstream, type = "set"):
 
36
        """
 
37
        @type xmlstream: L{xmlstream.XmlStream}
 
38
        @param xmlstream: XmlStream to use for transmission of this IQ
 
39
 
 
40
        @type type: L{str}
 
41
        @param type: IQ type identifier ('get' or 'set')
 
42
        """
 
43
 
 
44
        domish.Element.__init__(self, ("jabber:client", "iq"))
 
45
        self.addUniqueId()
 
46
        self["type"] = type
 
47
        self._xmlstream = xmlstream
 
48
        self.callbacks = utility.CallbackList()
 
49
 
 
50
    def addCallback(self, fn, *args, **kwargs):
 
51
        """
 
52
        Register a callback for notification when the IQ result is available.
 
53
        """
 
54
 
 
55
        self.callbacks.addCallback(True, fn, *args, **kwargs)
 
56
 
 
57
    def send(self, to = None):
 
58
        """
 
59
        Call this method to send this IQ request via the associated XmlStream.
 
60
 
 
61
        @param to: Jabber ID of the entity to send the request to
 
62
        @type to: L{str}
 
63
 
 
64
        @returns: Callback list for this IQ. Any callbacks added to this list
 
65
                  will be fired when the result comes back.
 
66
        """
 
67
        if to != None:
 
68
            self["to"] = to
 
69
        self._xmlstream.addOnetimeObserver("/iq[@id='%s']" % self["id"], \
 
70
                                                             self._resultEvent)
 
71
        self._xmlstream.send(self)
 
72
 
 
73
    def _resultEvent(self, iq):
 
74
        self.callbacks.callback(iq)
 
75
        self.callbacks = None
 
76
 
 
77
 
 
78
 
 
79
class IQAuthInitializer(object):
 
80
    """
 
81
    Non-SASL Authentication initializer for the initiating entity.
 
82
 
 
83
    This protocol is defined in
 
84
    U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>} and mainly serves for
 
85
    compatibility with pre-XMPP-1.0 server implementations.
 
86
    """
 
87
 
 
88
    INVALID_USER_EVENT    = "//event/client/basicauth/invaliduser"
 
89
    AUTH_FAILED_EVENT     = "//event/client/basicauth/authfailed"
 
90
 
 
91
    def __init__(self, xs):
 
92
        self.xmlstream = xs
 
93
 
 
94
 
 
95
    def initialize(self):
 
96
        # Send request for auth fields
 
97
        iq = xmlstream.IQ(self.xmlstream, "get")
 
98
        iq.addElement(("jabber:iq:auth", "query"))
 
99
        jid = self.xmlstream.authenticator.jid
 
100
        iq.query.addElement("username", content = jid.user)
 
101
 
 
102
        d = iq.send()
 
103
        d.addCallbacks(self._cbAuthQuery, self._ebAuthQuery)
 
104
        return d
 
105
 
 
106
 
 
107
    def _cbAuthQuery(self, iq):
 
108
        jid = self.xmlstream.authenticator.jid
 
109
        password = self.xmlstream.authenticator.password
 
110
 
 
111
        # Construct auth request
 
112
        reply = xmlstream.IQ(self.xmlstream, "set")
 
113
        reply.addElement(("jabber:iq:auth", "query"))
 
114
        reply.query.addElement("username", content = jid.user)
 
115
        reply.query.addElement("resource", content = jid.resource)
 
116
 
 
117
        # Prefer digest over plaintext
 
118
        if DigestAuthQry.matches(iq):
 
119
            digest = xmlstream.hashPassword(self.xmlstream.sid, password)
 
120
            reply.query.addElement("digest", content = digest)
 
121
        else:
 
122
            reply.query.addElement("password", content = password)
 
123
 
 
124
        d = reply.send()
 
125
        d.addCallbacks(self._cbAuth, self._ebAuth)
 
126
        return d
 
127
 
 
128
 
 
129
    def _ebAuthQuery(self, failure):
 
130
        failure.trap(error.StanzaError)
 
131
        e = failure.value
 
132
        if e.condition == 'not-authorized':
 
133
            self.xmlstream.dispatch(e.stanza, self.INVALID_USER_EVENT)
 
134
        else:
 
135
            self.xmlstream.dispatch(e.stanza, self.AUTH_FAILED_EVENT)
 
136
 
 
137
        return failure
 
138
 
 
139
 
 
140
    def _cbAuth(self, iq):
 
141
        pass
 
142
 
 
143
 
 
144
    def _ebAuth(self, failure):
 
145
        failure.trap(error.StanzaError)
 
146
        self.xmlstream.dispatch(failure.value.stanza, self.AUTH_FAILED_EVENT)
 
147
        return failure
 
148
 
 
149
 
 
150
 
 
151
class BasicAuthenticator(xmlstream.ConnectAuthenticator):
 
152
    """
 
153
    Authenticates an XmlStream against a Jabber server as a Client.
 
154
 
 
155
    This only implements non-SASL authentication, per
 
156
    U{JEP-0078<http://www.jabber.org/jeps/jep-0078.html>}. Additionally, this
 
157
    authenticator provides the ability to perform inline registration, per
 
158
    U{JEP-0077<http://www.jabber.org/jeps/jep-0077.html>}.
 
159
 
 
160
    Under normal circumstances, the BasicAuthenticator generates the
 
161
    L{xmlstream.STREAM_AUTHD_EVENT} once the stream has authenticated. However,
 
162
    it can also generate other events, such as:
 
163
      - L{INVALID_USER_EVENT} : Authentication failed, due to invalid username
 
164
      - L{AUTH_FAILED_EVENT} : Authentication failed, due to invalid password
 
165
      - L{REGISTER_FAILED_EVENT} : Registration failed
 
166
 
 
167
    If authentication fails for any reason, you can attempt to register by
 
168
    calling the L{registerAccount} method. If the registration succeeds, a
 
169
    L{xmlstream.STREAM_AUTHD_EVENT} will be fired. Otherwise, one of the above
 
170
    errors will be generated (again).
 
171
    """
 
172
 
 
173
    namespace = "jabber:client"
 
174
 
 
175
    INVALID_USER_EVENT    = IQAuthInitializer.INVALID_USER_EVENT
 
176
    AUTH_FAILED_EVENT     = IQAuthInitializer.AUTH_FAILED_EVENT
 
177
    REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
 
178
 
 
179
    def __init__(self, jid, password):
 
180
        xmlstream.ConnectAuthenticator.__init__(self, jid.host)
 
181
        self.jid = jid
 
182
        self.password = password
 
183
 
 
184
    def associateWithStream(self, xs):
 
185
        xs.version = (0, 0)
 
186
        xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
 
187
 
 
188
        inits = [ (xmlstream.TLSInitiatingInitializer, False),
 
189
                  (IQAuthInitializer, True),
 
190
                ]
 
191
 
 
192
        for initClass, required in inits:
 
193
            init = initClass(xs)
 
194
            init.required = required
 
195
            xs.initializers.append(init)
 
196
 
 
197
    # TODO: move registration into an Initializer?
 
198
 
 
199
    def registerAccount(self, username = None, password = None):
 
200
        if username:
 
201
            self.jid.user = username
 
202
        if password:
 
203
            self.password = password
 
204
 
 
205
        iq = IQ(self.xmlstream, "set")
 
206
        iq.addElement(("jabber:iq:register", "query"))
 
207
        iq.query.addElement("username", content = self.jid.user)
 
208
        iq.query.addElement("password", content = self.password)
 
209
 
 
210
        iq.addCallback(self._registerResultEvent)
 
211
 
 
212
        iq.send()
 
213
 
 
214
    def _registerResultEvent(self, iq):
 
215
        if iq["type"] == "result":
 
216
            # Registration succeeded -- go ahead and auth
 
217
            self.streamStarted()
 
218
        else:
 
219
            # Registration failed
 
220
            self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
 
221
 
 
222
 
 
223
 
 
224
class CheckVersionInitializer(object):
 
225
    """
 
226
    Initializer that checks if the minimum common stream version number is 1.0.
 
227
    """
 
228
 
 
229
    def __init__(self, xs):
 
230
        self.xmlstream = xs
 
231
 
 
232
 
 
233
    def initialize(self):
 
234
        if self.xmlstream.version < (1, 0):
 
235
            raise error.StreamError('unsupported-version')
 
236
 
 
237
 
 
238
 
 
239
class BindInitializer(xmlstream.BaseFeatureInitiatingInitializer):
 
240
    """
 
241
    Initializer that implements Resource Binding for the initiating entity.
 
242
 
 
243
    This protocol is documented in U{RFC 3920, section
 
244
    7<http://www.xmpp.org/specs/rfc3920.html#bind>}.
 
245
    """
 
246
 
 
247
    feature = (NS_XMPP_BIND, 'bind')
 
248
 
 
249
    def start(self):
 
250
        iq = xmlstream.IQ(self.xmlstream, 'set')
 
251
        bind = iq.addElement((NS_XMPP_BIND, 'bind'))
 
252
        resource = self.xmlstream.authenticator.jid.resource
 
253
        if resource:
 
254
            bind.addElement('resource', content=resource)
 
255
        d = iq.send()
 
256
        d.addCallback(self.onBind)
 
257
        return d
 
258
 
 
259
 
 
260
    def onBind(self, iq):
 
261
        if iq.bind:
 
262
            self.xmlstream.authenticator.jid = JID(unicode(iq.bind.jid))
 
263
 
 
264
 
 
265
 
 
266
class SessionInitializer(xmlstream.BaseFeatureInitiatingInitializer):
 
267
    """
 
268
    Initializer that implements session establishment for the initiating
 
269
    entity.
 
270
 
 
271
    This protocol is defined in U{RFC 3921, section
 
272
    3<http://www.xmpp.org/specs/rfc3921.html#session>}.
 
273
    """
 
274
 
 
275
    feature = (NS_XMPP_SESSION, 'session')
 
276
 
 
277
    def start(self):
 
278
        iq = xmlstream.IQ(self.xmlstream, 'set')
 
279
        session = iq.addElement((NS_XMPP_SESSION, 'session'))
 
280
        return iq.send()
 
281
 
 
282
 
 
283
 
 
284
def XMPPClientFactory(jid, password):
 
285
    """
 
286
    Client factory for XMPP 1.0 (only).
 
287
 
 
288
    This returns a L{xmlstream.XmlStreamFactory} with an L{XMPPAuthenticator}
 
289
    object to perform the stream initialization steps (such as authentication}.
 
290
 
 
291
    @see: The notes at L{XMPPAuthenticator} describe how the L{jid} and
 
292
    L{password} parameters are to be used.
 
293
 
 
294
    @param jid: Jabber ID to connect with.
 
295
    @type jid: L{jid.JID}
 
296
    @param password: password to authenticate with.
 
297
    @type password: L{unicode}
 
298
    @return: XML stream factory.
 
299
    @rtype: L{xmlstream.XmlStreamFactory}
 
300
    """
 
301
    a = XMPPAuthenticator(jid, password)
 
302
    return xmlstream.XmlStreamFactory(a)
 
303
 
 
304
 
 
305
 
 
306
class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
 
307
    """
 
308
    Initializes an XmlStream connecting to an XMPP server as a Client.
 
309
 
 
310
    This authenticator performs the initialization steps needed to start
 
311
    exchanging XML stanzas with an XMPP server as an XMPP client. It checks if
 
312
    the server advertises XML stream version 1.0, negotiates TLS (when
 
313
    available), performs SASL authentication, binds a resource and establishes
 
314
    a session.
 
315
 
 
316
    Upon successful stream initialization, the L{xmlstream.STREAM_AUTHD_EVENT}
 
317
    event will be dispatched through the XML stream object. Otherwise, the
 
318
    L{xmlstream.INIT_FAILED_EVENT} event will be dispatched with a failure
 
319
    object.
 
320
 
 
321
    After inspection of the failure, initialization can then be restarted by
 
322
    calling L{initializeStream}. For example, in case of authentication
 
323
    failure, a user may be given the opportunity to input the correct password.
 
324
    By setting the L{password} instance variable and restarting initialization,
 
325
    the stream authentication step is then retried, and subsequent steps are
 
326
    performed if succesful.
 
327
 
 
328
    @ivar jid: Jabber ID to authenticate with. This may contain a resource
 
329
               part, as a suggestion to the server for resource binding. A
 
330
               server may override this, though. If the resource part is left
 
331
               off, the server will generate a unique resource identifier.
 
332
               The server will always return the full Jabber ID in the
 
333
               resource binding step, and this is stored in this instance
 
334
               variable.
 
335
    @type jid: L{jid.JID}
 
336
    @ivar password: password to be used during SASL authentication.
 
337
    @type password: L{unicode}
 
338
    """
 
339
 
 
340
    namespace = 'jabber:client'
 
341
 
 
342
    def __init__(self, jid, password):
 
343
        xmlstream.ConnectAuthenticator.__init__(self, jid.host)
 
344
        self.jid = jid
 
345
        self.password = password
 
346
 
 
347
 
 
348
    def associateWithStream(self, xs):
 
349
        """
 
350
        Register with the XML stream.
 
351
 
 
352
        Populates stream's list of initializers, along with their
 
353
        requiredness. This list is used by
 
354
        L{ConnectAuthenticator.initializeStream} to perform the initalization
 
355
        steps.
 
356
        """
 
357
        xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
 
358
 
 
359
        xs.initializers = [CheckVersionInitializer(xs)]
 
360
        inits = [ (xmlstream.TLSInitiatingInitializer, False),
 
361
                  (sasl.SASLInitiatingInitializer, True),
 
362
                  (BindInitializer, False),
 
363
                  (SessionInitializer, False),
 
364
                ]
 
365
 
 
366
        for initClass, required in inits:
 
367
            init = initClass(xs)
 
368
            init.required = required
 
369
            xs.initializers.append(init)