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

Source Code for Module pyxmpp.sasl.core

  1  # 
  2  # (C) Copyright 2003-2010 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  __docformat__="restructuredtext en" 
 23   
 24  import random 
 25  import logging 
 26  from binascii import b2a_base64 
 27   
28 -class PasswordManager:
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."""
36 - def __init__(self):
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
67 - def check_password(self,username,password,realm=None):
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
98 - def get_realms(self):
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
108 - def choose_realm(self,realm_list):
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
128 - def check_authzid(self,authzid,extra_info=None):
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
156 - def get_serv_type(self):
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
164 - def get_serv_host(self):
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
172 - def get_serv_name(self):
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
180 - def generate_nonce(self):
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 # FIXME: use some better RNG (/dev/urandom maybe) 188 r1=str(random.random())[2:] 189 r2=str(random.random())[2:] 190 return r1+r2
191
192 -class Reply:
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
211 - def base64(self):
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
224 -class Challenge(Reply):
225 """The challenge SASL message (server's challenge for the client)."""
226 - def __init__(self,data):
227 """Initialize the `Challenge` object.""" 228 Reply.__init__(self,data)
229 - def __repr__(self):
230 return "<sasl.Challenge: %r>" % (self.data,)
231
232 -class Response(Reply):
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)
237 - def __repr__(self):
238 return "<sasl.Response: %r>" % (self.data,)
239
240 -class Failure(Reply):
241 """The failure SASL message. 242 243 :Ivariables: 244 - `reason`: the failure reason. 245 :Types: 246 - `reason`: unicode."""
247 - def __init__(self,reason,encode=True):
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
256 - def __repr__(self):
257 return "<sasl.Failure: %r>" % (self.reason,)
258
259 -class Success(Reply):
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
279 - def __repr__(self):
280 return "<sasl.Success: authzid: %r data: %r>" % (self.authzid,self.data)
281
282 -class ClientAuthenticator:
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
289 - def __init__(self,password_manager):
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
316 - def challenge(self,challenge):
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
329 - def finish(self,data):
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
342 -class ServerAuthenticator:
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
349 - def __init__(self,password_manager):
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
375 - def response(self,response):
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 # vi: sts=4 et sw=4 389