]>
code.delx.au - pymsnt/blob - src/tlib/jabber/client.py
1 # -*- test-case-name: twisted.test.test_jabbercomponent -*-
3 # Twisted, the Framework of Your Internet
4 # Copyright (C) 2001 Matthew W. Lefkowitz
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of version 2.1 of the GNU Lesser General Public
8 # License as published by the Free Software Foundation.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 from tlib
import domish
20 from twisted
.xish
import xpath
, utility
21 from tlib
import xmlstream
23 DigestAuthQry
= xpath
.intern("/iq/query/digest")
24 PlaintextAuthQry
= xpath
.intern("/iq/query/password")
26 def basicClientFactory(jid
, secret
):
27 a
= BasicAuthenticator(jid
, secret
)
28 return xmlstream
.XmlStreamFactory(a
)
30 class IQ(domish
.Element
):
31 """ Wrapper for a Info/Query packet
33 This provides the necessary functionality to send IQs and get notified
34 when a result comes back. It's a subclass from domish.Element, so you can
35 use the standard DOM manipulation calls to add data to the outbound
38 @type callbacks: C{hemp.utility.CallbackList}
39 @cvar callbacks: Callback list to be notified when response comes back
42 def __init__(self
, xmlstream
, type = "set"):
44 @type xmlstream: C{XmlStream}
45 @param xmlstream: XmlStream to use for transmission of this IQ
48 @param type: IQ type identifier ('get' or 'set')
51 domish
.Element
.__init
__(self
, ("jabber:client", "iq"))
54 self
._xmlstream
= xmlstream
55 self
.callbacks
= utility
.CallbackList()
57 def addCallback(self
, fn
, *args
, **kwargs
):
59 Register a callback for notification when the IQ result
63 self
.callbacks
.addCallback(True, fn
, *args
, **kwargs
)
65 def send(self
, to
= None):
67 Call this method to send this IQ request via the associated XmlStream
70 @type to: Jabber ID of the entity to send the request to
72 @returns: Callback list for this IQ. Any callbacks added to this list will
73 be fired when the result comes back.
77 self
._xmlstream
.addOnetimeObserver("/iq[@id='%s']" % self
["id"], \
79 self
._xmlstream
.send(self
.toXml())
81 def _resultEvent(self
, iq
):
82 self
.callbacks
.callback(iq
)
85 class BasicAuthenticator(xmlstream
.ConnectAuthenticator
):
86 """ Authenticates an XmlStream against a Jabber server as a Client
88 This only implements non-SASL authentication, per
89 U{JEP 78<http://www.jabber.org/jeps/jep-0078.html>}. Additionally, this
90 authenticator provides the ability to perform inline registration, per
91 U{JEP 77<http://www.jabber.org/jeps/jep-0077.html>}.
93 Under normal circumstances, the BasicAuthenticator generates the L{STREAM_AUTHD_EVENT}
94 once the stream has authenticated. However, it can also generate other events, such
96 - L{INVALID_USER_EVENT} : Authentication failed, due to invalid username
97 - L{AUTH_FAILED_EVENT} : Authentication failed, due to invalid password
98 - L{REGISTER_FAILED_EVENT} : Registration failed
100 If authentication fails for any reason, you can attempt to register by calling
101 the L{registerAccount} method. If the registration succeeds, a L{STREAM_AUTHD_EVENT}
102 will be fired. Otherwise, one of the above errors will be generated (again).
105 namespace
= "jabber:client"
107 INVALID_USER_EVENT
= "//event/client/basicauth/invaliduser"
108 AUTH_FAILED_EVENT
= "//event/client/basicauth/authfailed"
109 REGISTER_FAILED_EVENT
= "//event/client/basicauth/registerfailed"
111 def __init__(self
, jid
, password
):
112 xmlstream
.ConnectAuthenticator
.__init
__(self
, jid
.host
)
114 self
.password
= password
116 def streamStarted(self
, rootelem
):
117 # Send request for auth fields
118 iq
= IQ(self
.xmlstream
, "get")
119 iq
.addElement(("jabber:iq:auth", "query"))
120 iq
.query
.addElement("username", content
= self
.jid
.user
)
121 iq
.addCallback(self
._authQueryResultEvent
)
124 def _authQueryResultEvent(self
, iq
):
125 if iq
["type"] == "result":
126 # Construct auth request
127 iq
= IQ(self
.xmlstream
, "set")
128 iq
.addElement(("jabber:iq:auth", "query"))
129 iq
.query
.addElement("username", content
= self
.jid
.user
)
130 iq
.query
.addElement("resource", content
= self
.jid
.resource
)
132 # Prefer digest over plaintext
133 if DigestAuthQry
.matches(iq
):
134 digest
= xmlstream
.hashPassword(self
.xmlstream
.sid
, self
.password
)
135 iq
.query
.addElement("digest", content
= digest
)
137 iq
.query
.addElement("password", content
= self
.password
)
139 iq
.addCallback(self
._authResultEvent
)
142 # Check for 401 -- Invalid user
143 if iq
.error
["code"] == "401":
144 self
.xmlstream
.dispatch(iq
, self
.INVALID_USER_EVENT
)
146 self
.xmlstream
.dispatch(iq
, self
.AUTH_FAILED_EVENT
)
148 def _authResultEvent(self
, iq
):
149 if iq
["type"] == "result":
150 self
.xmlstream
.dispatch(self
.xmlstream
, xmlstream
.STREAM_AUTHD_EVENT
)
152 self
.xmlstream
.dispatch(iq
, self
.AUTH_FAILED_EVENT
)
154 def registerAccount(self
, username
= None, password
= None):
156 self
.jid
.user
= username
158 self
.password
= password
160 iq
= IQ(self
.xmlstream
, "set")
161 iq
.addElement(("jabber:iq:register", "query"))
162 iq
.query
.addElement("username", content
= self
.jid
.user
)
163 iq
.query
.addElement("password", content
= self
.password
)
165 iq
.addCallback(self
._registerResultEvent
)
169 def _registerResultEvent(self
, iq
):
170 if iq
["type"] == "result":
171 # Registration succeeded -- go ahead and auth
172 self
.streamStarted(None)
174 # Registration failed
175 self
.xmlstream
.dispatch(iq
, self
.REGISTER_FAILED_EVENT
)