1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Base classes for PyXMPP SASL implementation.
18
19 Normative reference:
20 - `RFC 2222 <http://www.ietf.org/rfc/rfc2222.txt>`__
21 """
22 __docformat__="restructuredtext en"
23
24 import random
25 import logging
26 from binascii import b2a_base64
27
29 """Base class for password managers.
30
31 Password manager is an object responsible for providing or verification
32 of authentication credentials.
33
34 All the methods of `PasswordManager` class may be overriden in derived
35 classes for specific authentication and authorization policy."""
37 """Initialize a `PasswordManager` object."""
38 pass
39
40 - def get_password(self,username,realm=None,acceptable_formats=("plain",)):
41 """Get the password for user authentication.
42
43 [both client or server]
44
45 By default returns (None, None) providing no password. Should be
46 overriden in derived classes.
47
48 :Parameters:
49 - `username`: the username for which the password is requested.
50 - `realm`: the authentication realm for which the password is
51 requested.
52 - `acceptable_formats`: a sequence of acceptable formats of the
53 password data. Could be "plain", "md5:user:realm:password" or any
54 other mechanism-specific encoding. This allows non-plain-text
55 storage of passwords. But only "plain" format will work with
56 all password authentication mechanisms.
57 :Types:
58 - `username`: `unicode`
59 - `realm`: `unicode`
60 - `acceptable_formats`: sequence of `str`
61
62 :return: the password and its encoding (format).
63 :returntype: `unicode`,`str` tuple."""
64 _unused, _unused, _unused = username, realm, acceptable_formats
65 return None,None
66
68 """Check the password validity.
69
70 [server only]
71
72 Used by plain-text authentication mechanisms.
73
74 Retrieve a "plain" password for the `username` and `realm` using
75 `self.get_password` and compare it with the password provided.
76
77 May be overrided e.g. to check the password against some external
78 authentication mechanism (PAM, LDAP, etc.).
79
80 :Parameters:
81 - `username`: the username for which the password verification is
82 requested.
83 - `password`: the password to verify.
84 - `realm`: the authentication realm for which the password
85 verification is requested.
86 :Types:
87 - `username`: `unicode`
88 - `password`: `unicode`
89 - `realm`: `unicode`
90
91 :return: `True` if the password is valid.
92 :returntype: `bool`"""
93 pw,format=self.get_password(username,realm,("plain",))
94 if pw and format=="plain" and pw==password:
95 return True
96 return False
97
99 """Get available realms list.
100
101 [server only]
102
103 :return: a list of realms available for authentication. May be empty --
104 the client may choose its own realm then or use no realm at all.
105 :returntype: `list` of `unicode`"""
106 return []
107
109 """Choose an authentication realm from the list provided by the server.
110
111 [client only]
112
113 By default return the first realm from the list or `None` if the list
114 is empty.
115
116 :Parameters:
117 - `realm_list`: the list of realms provided by a server.
118 :Types:
119 - `realm_list`: sequence of `unicode`
120
121 :return: the realm chosen.
122 :returntype: `unicode`"""
123 if realm_list:
124 return realm_list[0]
125 else:
126 return None
127
129 """Check if the authenticated entity is allowed to use given
130 authorization id.
131
132 [server only]
133
134 By default return `True` if the `authzid` is `None` or empty or it is
135 equal to extra_info["username"] (if the latter is present).
136
137 :Parameters:
138 - `authzid`: an authorization id.
139 - `extra_info`: information about an entity got during the
140 authentication process. This is a mapping with arbitrary,
141 mechanism-dependent items. Common keys are 'username' or
142 'realm'.
143 :Types:
144 - `authzid`: `unicode`
145 - `extra_info`: mapping
146
147 :return: `True` if the authenticated entity is authorized to use
148 the provided authorization id.
149 :returntype: `bool`"""
150 if not extra_info:
151 extra_info={}
152 return (not authzid
153 or extra_info.has_key("username")
154 and extra_info["username"]==authzid)
155
157 """Return the service type for DIGEST-MD5 'digest-uri' field.
158
159 Should be overriden in derived classes.
160
161 :return: the service type ("unknown" by default)"""
162 return "unknown"
163
165 """Return the host name for DIGEST-MD5 'digest-uri' field.
166
167 Should be overriden in derived classes.
168
169 :return: the host name ("unknown" by default)"""
170 return "unknown"
171
173 """Return the service name for DIGEST-MD5 'digest-uri' field.
174
175 Should be overriden in derived classes.
176
177 :return: the service name or `None` (which is the default)."""
178 return None
179
181 """Generate a random string for digest authentication challenges.
182
183 The string should be cryptographicaly secure random pattern.
184
185 :return: the string generated.
186 :returntype: `str`"""
187
188 r1=str(random.random())[2:]
189 r2=str(random.random())[2:]
190 return r1+r2
191
193 """Base class for SASL authentication reply objects.
194
195 :Ivariables:
196 - `data`: optional reply data.
197 - `encode`: whether to base64 encode the data or not
198 :Types:
199 - `data`: `str`
200 - `encode`; `bool`"""
201 - def __init__(self,data="", encode=True):
202 """Initialize the `Reply` object.
203
204 :Parameters:
205 - `data`: optional reply data.
206 :Types:
207 - `data`: `str`"""
208 self.data=data
209 self.encode=encode
210
212 """Base64-encode the data contained in the reply.
213
214 :return: base64-encoded data.
215 :returntype: `str`"""
216 if self.data is not None:
217 ret=b2a_base64(self.data)
218 if ret[-1]=='\n':
219 ret=ret[:-1]
220 return ret
221 else:
222 return None
223
225 """The challenge SASL message (server's challenge for the client)."""
227 """Initialize the `Challenge` object."""
228 Reply.__init__(self,data)
230 return "<sasl.Challenge: %r>" % (self.data,)
231
233 """The response SASL message (clients's reply the the server's challenge)."""
234 - def __init__(self,data="", encode=True):
235 """Initialize the `Response` object."""
236 Reply.__init__(self,data, encode)
238 return "<sasl.Response: %r>" % (self.data,)
239
241 """The failure SASL message.
242
243 :Ivariables:
244 - `reason`: the failure reason.
245 :Types:
246 - `reason`: unicode."""
248 """Initialize the `Failure` object.
249
250 :Parameters:
251 - `reason`: the failure reason.
252 :Types:
253 - `reason`: unicode."""
254 Reply.__init__(self,"",encode)
255 self.reason=reason
257 return "<sasl.Failure: %r>" % (self.reason,)
258
260 """The success SASL message (sent by the server on authentication success)."""
261 - def __init__(self,username,realm=None,authzid=None,data=None):
262 """Initialize the `Success` object.
263
264 :Parameters:
265 - `username`: authenticated username (authentication id).
266 - `realm`: authentication realm used.
267 - `authzid`: authorization id.
268 - `data`: the success data to be sent to the client.
269 :Types:
270 - `username`: `unicode`
271 - `realm`: `unicode`
272 - `authzid`: `unicode`
273 - `data`: `str`
274 """
275 Reply.__init__(self,data)
276 self.username=username
277 self.realm=realm
278 self.authzid=authzid
280 return "<sasl.Success: authzid: %r data: %r>" % (self.authzid,self.data)
281
283 """Base class for client authenticators.
284
285 A client authenticator class is a client-side implementation of a SASL
286 mechanism. One `ClientAuthenticator` object may be used for one
287 client authentication process."""
288
290 """Initialize a `ClientAuthenticator` object.
291
292 :Parameters:
293 - `password_manager`: a password manager providing authentication
294 credentials.
295 :Types:
296 - `password_manager`: `PasswordManager`"""
297 self.password_manager=password_manager
298 self.__logger=logging.getLogger("pyxmpp.sasl.ClientAuthenticator")
299
300 - def start(self,username,authzid):
301 """Start the authentication process.
302
303 :Parameters:
304 - `username`: the username (authentication id).
305 - `authzid`: the authorization id requester.
306 :Types:
307 - `username`: `unicode`
308 - `authzid`: `unicode`
309
310 :return: the initial response to send to the server or a failuer
311 indicator.
312 :returntype: `Response` or `Failure`"""
313 _unused, _unused = username, authzid
314 return Failure("Not implemented")
315
317 """Process the server's challenge.
318
319 :Parameters:
320 - `challenge`: the challenge.
321 :Types:
322 - `challenge`: `str`
323
324 :return: the response or a failure indicator.
325 :returntype: `Response` or `Failure`"""
326 _unused = challenge
327 return Failure("Not implemented")
328
330 """Handle authentication succes information from the server.
331
332 :Parameters:
333 - `data`: the optional additional data returned with the success.
334 :Types:
335 - `data`: `str`
336
337 :return: success or failure indicator.
338 :returntype: `Success` or `Failure`"""
339 _unused = data
340 return Failure("Not implemented")
341
343 """Base class for server authenticators.
344
345 A server authenticator class is a server-side implementation of a SASL
346 mechanism. One `ServerAuthenticator` object may be used for one
347 client authentication process."""
348
350 """Initialize a `ServerAuthenticator` object.
351
352 :Parameters:
353 - `password_manager`: a password manager providing authentication
354 credential verfication.
355 :Types:
356 - `password_manager`: `PasswordManager`"""
357 self.password_manager=password_manager
358 self.__logger=logging.getLogger("pyxmpp.sasl.ServerAuthenticator")
359
360 - def start(self,initial_response):
361 """Start the authentication process.
362
363 :Parameters:
364 - `initial_response`: the initial response send by the client with
365 the authentication request.
366
367 :Types:
368 - `initial_response`: `str`
369
370 :return: a challenge, a success or a failure indicator.
371 :returntype: `Challenge` or `Failure` or `Success`"""
372 _unused = initial_response
373 return Failure("not-authorized")
374
376 """Process a response from a client.
377
378 :Parameters:
379 - `response`: the response from the client to our challenge.
380 :Types:
381 - `response`: `str`
382
383 :return: a challenge, a success or a failure indicator.
384 :returntype: `Challenge` or `Success` or `Failure`"""
385 _unused = response
386 return Failure("not-authorized")
387
388
389