]> code.delx.au - pymsnt/blob - src/tlib/jabber/client.py
Reimporting (0.9.5)
[pymsnt] / src / tlib / jabber / client.py
1 # -*- test-case-name: twisted.test.test_jabbercomponent -*-
2 #
3 # Twisted, the Framework of Your Internet
4 # Copyright (C) 2001 Matthew W. Lefkowitz
5 #
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.
9 #
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.
14 #
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
18
19 from tlib import domish
20 from twisted.xish import xpath, utility
21 from tlib import xmlstream
22
23 DigestAuthQry = xpath.intern("/iq/query/digest")
24 PlaintextAuthQry = xpath.intern("/iq/query/password")
25
26 def basicClientFactory(jid, secret):
27 a = BasicAuthenticator(jid, secret)
28 return xmlstream.XmlStreamFactory(a)
29
30 class IQ(domish.Element):
31 """ Wrapper for a Info/Query packet
32
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
36 request.
37
38 @type callbacks: C{hemp.utility.CallbackList}
39 @cvar callbacks: Callback list to be notified when response comes back
40
41 """
42 def __init__(self, xmlstream, type = "set"):
43 """
44 @type xmlstream: C{XmlStream}
45 @param xmlstream: XmlStream to use for transmission of this IQ
46
47 @type type: C{str}
48 @param type: IQ type identifier ('get' or 'set')
49
50 """
51 domish.Element.__init__(self, ("jabber:client", "iq"))
52 self.addUniqueId()
53 self["type"] = type
54 self._xmlstream = xmlstream
55 self.callbacks = utility.CallbackList()
56
57 def addCallback(self, fn, *args, **kwargs):
58 """
59 Register a callback for notification when the IQ result
60 is available.
61
62 """
63 self.callbacks.addCallback(True, fn, *args, **kwargs)
64
65 def send(self, to = None):
66 """
67 Call this method to send this IQ request via the associated XmlStream
68
69 @type to: C{str}
70 @type to: Jabber ID of the entity to send the request to
71
72 @returns: Callback list for this IQ. Any callbacks added to this list will
73 be fired when the result comes back.
74 """
75 if to != None:
76 self["to"] = to
77 self._xmlstream.addOnetimeObserver("/iq[@id='%s']" % self["id"], \
78 self._resultEvent)
79 self._xmlstream.send(self.toXml())
80
81 def _resultEvent(self, iq):
82 self.callbacks.callback(iq)
83 self.callbacks = None
84
85 class BasicAuthenticator(xmlstream.ConnectAuthenticator):
86 """ Authenticates an XmlStream against a Jabber server as a Client
87
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>}.
92
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
95 as:
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
99
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).
103
104 """
105 namespace = "jabber:client"
106
107 INVALID_USER_EVENT = "//event/client/basicauth/invaliduser"
108 AUTH_FAILED_EVENT = "//event/client/basicauth/authfailed"
109 REGISTER_FAILED_EVENT = "//event/client/basicauth/registerfailed"
110
111 def __init__(self, jid, password):
112 xmlstream.ConnectAuthenticator.__init__(self, jid.host)
113 self.jid = jid
114 self.password = password
115
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)
122 iq.send()
123
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)
131
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)
136 else:
137 iq.query.addElement("password", content = self.password)
138
139 iq.addCallback(self._authResultEvent)
140 iq.send()
141 else:
142 # Check for 401 -- Invalid user
143 if iq.error["code"] == "401":
144 self.xmlstream.dispatch(iq, self.INVALID_USER_EVENT)
145 else:
146 self.xmlstream.dispatch(iq, self.AUTH_FAILED_EVENT)
147
148 def _authResultEvent(self, iq):
149 if iq["type"] == "result":
150 self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
151 else:
152 self.xmlstream.dispatch(iq, self.AUTH_FAILED_EVENT)
153
154 def registerAccount(self, username = None, password = None):
155 if username:
156 self.jid.user = username
157 if password:
158 self.password = password
159
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)
164
165 iq.addCallback(self._registerResultEvent)
166
167 iq.send()
168
169 def _registerResultEvent(self, iq):
170 if iq["type"] == "result":
171 # Registration succeeded -- go ahead and auth
172 self.streamStarted(None)
173 else:
174 # Registration failed
175 self.xmlstream.dispatch(iq, self.REGISTER_FAILED_EVENT)
176