"""$URL: svn+ssh://svn/repos/trunk/dulcinea/lib/user.py $
$Id: user.py 24784 2004-07-23 16:33:04Z nascheme $
"""

import re, sha, time, binascii
from durus.persistent_dict import PersistentDict
from dulcinea.base import DulcineaPersistent
from dulcinea.errors import ConflictError
from dulcinea.sort import lexical_sort
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

    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

    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 check_new_password(self, new_password):
        """(string) -> string
        Check if a new password is valid.  Returns the empty string if the
        password is okay otherwise returns a string that describes what is
        wrong with the entered password.
        """
        return ""

    def get_email(self):
        return None

    def format_realname(self):
        return ''

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

    def is_null(self):
        return self.id == ''

    def __nonzero__(self):
        return not self.is_null()

    def is_system(self):
        return self.id == 'SYSTEM'


class DulcineaUserDatabase(DulcineaPersistent):
    """
    Class to hold all users in the system.  User IDs are always looked
    up in the user database, so you will generally not be able to use
    a user until it has been added to the user database.

    Instance attributes:
      users : PersistentDict { user_id:string : user:DulcineaUser }
        all known users
    """

    user_class = DulcineaUser

    def __init__(self):
        self.users =  PersistentDict()

    def get_all_users(self):
        return self.users.values()

    def get_users(self, sort=0):
        users = [user for user in self.users.itervalues()
                 if not (user.is_null() or user.is_system())]
        if sort:
            users = lexical_sort(users)
        return users

    def get_user(self, user_id):
        """Return the User object with id 'user_id', or None if no such
        user."""
        return self.users.get(user_id)

    def __getitem__(self, user_id):
        return self.users[user_id]

    def add_user(self, user):
        """Add User object 'user' to the user database.  Raises
        ConflictError if there is already a user with the same user
        ID.
        """
        if self.users.has_key(user.id):
            raise ConflictError, \
                  "user '%s' already exists in the user database" % \
                  user.id
        self.users[user.id] = user

    def get_admin(self):
        """Subclasses should override to return a PermissionManager
        """
        return None

    def get_null_user(self):
        user = self.users.get('')
        if user is None:
            user = self.user_class(user_id='')
            self.add_user(user)
        return user
