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