"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/user.py $
$Id: user.py 23216 2003-11-21 21:00:24Z dbinger $

Provides a basic DulcineaUser class (user ID and password hash only)
along with tools for hashing and generating passwords as well as
a more heavyweight Contact class that uses the PermissionManager to
determine admin capability and has an Address associated with it
"""

import re, sha, time, binascii
from dulcinea import local
from dulcinea.address import Addressable
from dulcinea.base import DulcineaPersistent
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.

        """
        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*"

    format = format_realname = __str__ # subclasses should override

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

    def set_id(self, user_id):
        typecheck(user_id, str)
        assert self.id is None, "'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

    def get_email(self):
        return None

    def format_realname(self):
        return ''

    def is_admin(self):
        """Override if you want superiority."""
        return False


class Contact(DulcineaUser, Addressable):
    """A more heavyweight user object that contains an Address object
    and relys on a PermissionManager to determine if the user has admin power

    Instance attributes:
      email : string
        the user's Internet email address
    """
    def __init__(self, user_id=None):
        DulcineaUser.__init__(self, user_id=user_id)
        Addressable.__init__(self)
        self.email = None

    def format_realname(self):
        return (self.address and self.address.contact_name) or ''

    def set_email(self, email):
        self.email = email

    def get_email(self):
        return self.email

    def is_admin(self):
        return local.get_user_database().get_admin().directly_grants(
            'staff', self)
