pyxmpp.sasl.core

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  __revision__="$Id: core.py 714 2010-04-05 10:20:10Z 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