Package pyxmpp :: Package sasl :: Module core
[hide private]

Source Code for Module pyxmpp.sasl.core

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