# -*- test-case-name: twisted.test.test_jabbercomponent -*-
#
# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from twisted.xish import domish, xpath, utility
from twisted.protocols import xmlstream
DigestAuthQry = xpath.intern("/iq/query/digest")
PlaintextAuthQry = xpath.intern("/iq/query/password")
def basicClientFactory(jid, secret):
a = BasicAuthenticator(jid, secret)
return xmlstream.XmlStreamFactory(a)
class IQ(domish.Element):
""" Wrapper for a Info/Query packet
This provides the necessary functionality to send IQs and get notified
when a result comes back. It's a subclass from domish.Element, so you can
use the standard DOM manipulation calls to add data to the outbound
request.
@type callbacks: C{hemp.utility.CallbackList}
@cvar callbacks: Callback list to be notified when response comes back
"""
def __init__(self, xmlstream, type = "set"):
"""
@type xmlstream: C{XmlStream}
@param xmlstream: XmlStream to use for transmission of this IQ
@type type: C{str}
@param type: IQ type identifier ('get' or 'set')
"""
domish.Element.__init__(self, ("jabber:client", "iq"))
self.addUniqueId()
self["type"] = type
self._xmlstream = xmlstream
self.callbacks = utility.CallbackList()
def addCallback(self, fn, *args, **kwargs):
"""
Register a callback for notification when the IQ result
is available.
"""
self.callbacks.addCallback(True, fn, *args, **kwargs)
def send(self, to = None):
"""
Call this method to send this IQ request via the associated XmlStream
@type to: C{str}
@type to: Jabber ID of the entity to send the request to
@returns: Callback list for this IQ. Any callbacks added to this list will
be fired when the result comes back.
"""
if to != None:
self["to"] = to
self._xmlstream.addOnetimeObserver("/iq[@id='%s']" % self["id"], \
self._resultEvent)
self._xmlstream.send(self.toXml())
def _resultEvent(self, iq):
self.callbacks.callback(iq)
self.callbacks = None
class BasicAuthenticator(xmlstream.ConnectAuthenticator):
""" Authenticates an XmlStream against a Jabber server as a Client
This only implements non-SASL authentication, per
U{JEP 78<http://www.jabber.org/jeps/jep-0078.html>}. Additionally, this
authenticator provides the ability to perform inline registration, per
U{JEP 77<http://www.jabber.org/jeps/jep-0077.html>}.
Under normal circumstances, the BasicAuthenticator generates the L{STREAM_AUTHD_EVENT}
once the stream has authenticated. However, it can also generate other events, such
as:
- L{INVALID_USER_EVENT} : Authentication failed, due to invalid username
- L{AUTH_FAILED_EVENT} : Authentication failed, due to invalid password
- L{REGISTER_FAILED_EVENT} : Registration failed
If authentication fails for any reason, you can attempt to register by calling
the L{registerAccount} method. If the registration succeeds, a L{STREAM_AUTHD_EVENT}
will be fired. Otherwise, one of the above errors will be generated (again).
"""
namespace = "jabber:client"
INVALID_USER_EVENT = "//event/client/basicauth/invaliduser"
AUTH_FAILED_EVENT = "//event/client/basicauth/authfailed"
REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
def __init__(self, jid, password):
xmlstream.ConnectAuthenticator.__init__(self, jid.host)
self.jid = jid
self.password = password
def streamStarted(self, rootelem):
# Send request for auth fields
iq = IQ(self.xmlstream, "get")
iq.addElement(("jabber:iq:auth", "query"))
iq.query.addElement("username", content = self.jid.user)
iq.addCallback(self._authQueryResultEvent)
iq.send()
def _authQueryResultEvent(self, iq):
if iq["type"] == "result":
# Construct auth request
iq = IQ(self.xmlstream, "set")
iq.addElement(("jabber:iq:auth", "query"))
iq.query.addElement("username", content = self.jid.user)
iq.query.addElement("resource", content = self.jid.resource)
# Prefer digest over plaintext
if DigestAuthQry.matches(iq):
digest = xmlstream.hashPassword(self.xmlstream.sid, self.password)
iq.query.addElement("digest", content = digest)
else:
iq.query.addElement("password", content = self.password)
iq.addCallback(self._authResultEvent)
iq.send()
else:
# Check for 401 -- Invalid user
if iq.error["code"] == "401":
self.xmlstream.dispatch(iq, self.INVALID_USER_EVENT)
else:
self.xmlstream.dispatch(iq, self.AUTH_FAILED_EVENT)
def _authResultEvent(self, iq):
if iq["type"] == "result":
self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
else:
self.xmlstream.dispatch(iq, self.AUTH_FAILED_EVENT)
def registerAccount(self, username = None, password = None):
if username:
self.jid.user = username
if password:
self.password = password
iq = IQ(self.xmlstream, "set")
iq.addElement(("jabber:iq:register", "query"))
iq.query.addElement("username", content = self.jid.user)
iq.query.addElement("password", content = self.password)
iq.addCallback(self._registerResultEvent)
iq.send()
def _registerResultEvent(self, iq):
if iq["type"] == "result":
# Registration succeeded -- go ahead and auth
self.streamStarted(None)
else:
# Registration failed
self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
syntax highlighted by Code2HTML, v. 0.9.1