"""$URL: svn://svn/repos/trunk/dulcinea/lib/user.py $
$Id: user.py 21297 2003-04-08 18:44:36Z akuchlin $

Provides a basic DulcineaUser class (user ID and password hash only)
along with tools for hashing and generating passwords.
"""

import re, sha, time
from dulcinea.base import DulcineaPersistent, new_persistent_list
from dulcinea.errors import UsageError
from dulcinea.typeutils import typecheck


def hash_password(password):
    """Apply a one way hash function to a password and return the result."""
    return sha.new(password).hexdigest()


class PasswordGenerator:
    """
    Instance attributes:
      hash : any
    """
    default_seed = "iGcnvxFfyniL2w" # some random secret data

    def __init__(self, seed=default_seed):
        self.hash = sha.new(seed)
        self.hash.update(str(time.time()))

    def generate(self, seed="", length=6):
        """Generate a password.  Some effort is made to make it random.

        seed: if supplied the str() of this argument is used to update the
              generator state before generating the password.

        length: the maximum length of the password to return.

        """
        import binascii
        try:
            # Try to use /dev/urandom.
            self.hash.update(open("/dev/urandom", "rb").read(length))
        except IOError:
            # No source of random data.  This method will now only
            # generate passwords that look random if seed is kept secret.
            self.hash.update(str(time.time()))
        self.hash.update(str(seed))
        return binascii.b2a_base64(self.hash.digest())[:length]

_password_generator = PasswordGenerator()


class DulcineaUser (DulcineaPersistent):
    """Information for a registered user.

    Instance attributes:
      id : string
        unique identifier for this user
      password_hash : string
        the hashed version of the user's password, created using
        the hash_password function
    """

    user_id_re = re.compile('^[-A-Za-z0-9_@.]*$')

    def __init__ (self, user_id=None):
        self.id = None
        if user_id is not None:
            self.set_id(user_id)

        self.password_hash = None


    def __str__ (self):
        return self.id or "*no id*"

    # -- User ID methods -----------------------------------------------

    def set_id (self, user_id):
        typecheck(user_id, str)
        if self.id is not None:
            raise UsageError, "'id' may only be set once"
        if not self.user_id_re.match(user_id):
            raise ValueError(
                'Invalid user ID %r: can only contain '
                'letters, numbers, and "-_@."' % user_id)
        self.id = user_id

    def get_id (self):
        return self.id


    # -- Password methods ----------------------------------------------

    def set_password(self, new_password):
        """Set the user's password to 'new_password'."""
        self.password_hash = hash_password(new_password)

    def valid_password(self, password):
        """Return true if the provided password is correct."""
        return self.password_hash == hash_password(password)

    def generate_password (self):
        """Set the password to a random value and return the new password."""
        seed = self.id + str(self.password_hash)
        password = _password_generator.generate(seed)
        self.set_password(password)
        return password

