1
# -*- test-case-name: twisted.words.test.test_jabberclient -*-
3
# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
4
# See LICENSE for details.
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
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'
16
DigestAuthQry = xpath.internQuery("/iq/query/digest")
17
PlaintextAuthQry = xpath.internQuery("/iq/query/password")
19
def basicClientFactory(jid, secret):
20
a = BasicAuthenticator(jid, secret)
21
return xmlstream.XmlStreamFactory(a)
23
class IQ(domish.Element):
25
Wrapper for a Info/Query packet.
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.
31
@type callbacks: L{utility.CallbackList}
32
@cvar callbacks: Callback list to be notified when response comes back
35
def __init__(self, xmlstream, type = "set"):
37
@type xmlstream: L{xmlstream.XmlStream}
38
@param xmlstream: XmlStream to use for transmission of this IQ
41
@param type: IQ type identifier ('get' or 'set')
44
domish.Element.__init__(self, ("jabber:client", "iq"))
47
self._xmlstream = xmlstream
48
self.callbacks = utility.CallbackList()
50
def addCallback(self, fn, *args, **kwargs):
52
Register a callback for notification when the IQ result is available.
55
self.callbacks.addCallback(True, fn, *args, **kwargs)
57
def send(self, to = None):
59
Call this method to send this IQ request via the associated XmlStream.
61
@param to: Jabber ID of the entity to send the request to
64
@returns: Callback list for this IQ. Any callbacks added to this list
65
will be fired when the result comes back.
69
self._xmlstream.addOnetimeObserver("/iq[@id='%s']" % self["id"], \
71
self._xmlstream.send(self)
73
def _resultEvent(self, iq):
74
self.callbacks.callback(iq)
79
class IQAuthInitializer(object):
81
Non-SASL Authentication initializer for the initiating entity.
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.
88
INVALID_USER_EVENT = "//event/client/basicauth/invaliduser"
89
AUTH_FAILED_EVENT = "//event/client/basicauth/authfailed"
91
def __init__(self, xs):
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)
103
d.addCallbacks(self._cbAuthQuery, self._ebAuthQuery)
107
def _cbAuthQuery(self, iq):
108
jid = self.xmlstream.authenticator.jid
109
password = self.xmlstream.authenticator.password
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)
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)
122
reply.query.addElement("password", content = password)
125
d.addCallbacks(self._cbAuth, self._ebAuth)
129
def _ebAuthQuery(self, failure):
130
failure.trap(error.StanzaError)
132
if e.condition == 'not-authorized':
133
self.xmlstream.dispatch(e.stanza, self.INVALID_USER_EVENT)
135
self.xmlstream.dispatch(e.stanza, self.AUTH_FAILED_EVENT)
140
def _cbAuth(self, iq):
144
def _ebAuth(self, failure):
145
failure.trap(error.StanzaError)
146
self.xmlstream.dispatch(failure.value.stanza, self.AUTH_FAILED_EVENT)
151
class BasicAuthenticator(xmlstream.ConnectAuthenticator):
153
Authenticates an XmlStream against a Jabber server as a Client.
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>}.
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
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).
173
namespace = "jabber:client"
175
INVALID_USER_EVENT = IQAuthInitializer.INVALID_USER_EVENT
176
AUTH_FAILED_EVENT = IQAuthInitializer.AUTH_FAILED_EVENT
177
REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
179
def __init__(self, jid, password):
180
xmlstream.ConnectAuthenticator.__init__(self, jid.host)
182
self.password = password
184
def associateWithStream(self, xs):
186
xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
188
inits = [ (xmlstream.TLSInitiatingInitializer, False),
189
(IQAuthInitializer, True),
192
for initClass, required in inits:
194
init.required = required
195
xs.initializers.append(init)
197
# TODO: move registration into an Initializer?
199
def registerAccount(self, username = None, password = None):
201
self.jid.user = username
203
self.password = password
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)
210
iq.addCallback(self._registerResultEvent)
214
def _registerResultEvent(self, iq):
215
if iq["type"] == "result":
216
# Registration succeeded -- go ahead and auth
219
# Registration failed
220
self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
224
class CheckVersionInitializer(object):
226
Initializer that checks if the minimum common stream version number is 1.0.
229
def __init__(self, xs):
233
def initialize(self):
234
if self.xmlstream.version < (1, 0):
235
raise error.StreamError('unsupported-version')
239
class BindInitializer(xmlstream.BaseFeatureInitiatingInitializer):
241
Initializer that implements Resource Binding for the initiating entity.
243
This protocol is documented in U{RFC 3920, section
244
7<http://www.xmpp.org/specs/rfc3920.html#bind>}.
247
feature = (NS_XMPP_BIND, 'bind')
250
iq = xmlstream.IQ(self.xmlstream, 'set')
251
bind = iq.addElement((NS_XMPP_BIND, 'bind'))
252
resource = self.xmlstream.authenticator.jid.resource
254
bind.addElement('resource', content=resource)
256
d.addCallback(self.onBind)
260
def onBind(self, iq):
262
self.xmlstream.authenticator.jid = JID(unicode(iq.bind.jid))
266
class SessionInitializer(xmlstream.BaseFeatureInitiatingInitializer):
268
Initializer that implements session establishment for the initiating
271
This protocol is defined in U{RFC 3921, section
272
3<http://www.xmpp.org/specs/rfc3921.html#session>}.
275
feature = (NS_XMPP_SESSION, 'session')
278
iq = xmlstream.IQ(self.xmlstream, 'set')
279
session = iq.addElement((NS_XMPP_SESSION, 'session'))
284
def XMPPClientFactory(jid, password):
286
Client factory for XMPP 1.0 (only).
288
This returns a L{xmlstream.XmlStreamFactory} with an L{XMPPAuthenticator}
289
object to perform the stream initialization steps (such as authentication}.
291
@see: The notes at L{XMPPAuthenticator} describe how the L{jid} and
292
L{password} parameters are to be used.
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}
301
a = XMPPAuthenticator(jid, password)
302
return xmlstream.XmlStreamFactory(a)
306
class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
308
Initializes an XmlStream connecting to an XMPP server as a Client.
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
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
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.
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
335
@type jid: L{jid.JID}
336
@ivar password: password to be used during SASL authentication.
337
@type password: L{unicode}
340
namespace = 'jabber:client'
342
def __init__(self, jid, password):
343
xmlstream.ConnectAuthenticator.__init__(self, jid.host)
345
self.password = password
348
def associateWithStream(self, xs):
350
Register with the XML stream.
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
357
xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
359
xs.initializers = [CheckVersionInitializer(xs)]
360
inits = [ (xmlstream.TLSInitiatingInitializer, False),
361
(sasl.SASLInitiatingInitializer, True),
362
(BindInitializer, False),
363
(SessionInitializer, False),
366
for initClass, required in inits:
368
init.required = required
369
xs.initializers.append(init)