"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/user.py $
$Id: user.py 28604 2006-08-17 19:56:10Z rmasse $
"""
from dulcinea.sort import lexical_sort
from dulcinea.common import get_site_name, get_publisher
from durus.btree import BTree
from durus.persistent import Persistent
from qp.lib.spec import add_getters_and_setters, string, specify
from qp.lib.spec import spec, mapping, pattern, add_getters
from qp.pub.user import User
from qp.lib.util import randbytes
import sha, binascii


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

def get_realm():
    return get_site_name()

def get_user_db():
    return get_publisher().get_connection().get_root().get('user_db')

class DulcineaUser (User):
    """
    a registered user.
    """
    global_permissions = {
        "act-as":
            "Allow to act as another user.",
        "create-users":
            "Allow the creation of other users.",
        "manage-permissions":
            "Allow changing of permissions.",
        "staff":
            ("Is a member of the staff, with all of the privileges and "
             "responsibilities thereunto appertaining."),
        "system":
            "Allow to do things normally done by the software system.",
        }

    id_is = spec(
        pattern('^[-A-Za-z0-9_@. ]*$'),
        "unique among users here, spaces allowed")

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

    format = __str__ # subclasses should override

    def get_key(self):
        """ used for forming component representing this user in URLs
        """
        return self.get_id()

    def set_id(self, user_id):
        assert self.id == None
        specify(self, id=user_id)

    def set_password(self, new_password, realm=None, check=True):
        """Set the user's password to 'new_password'."""
        if check and self.check_new_password(new_password) != "":
            raise ValueError, 'invalid password'
        digester = self.get_digester()
        if not new_password:
            digester.discard_digest(realm or get_realm())
        else:
            digester.set_digest(realm or get_realm(),
                                hash_password(new_password))

    def has_password(self, password, realm=None):
        """Return true if the provided password is correct."""
        digester = self.get_digester()
        return digester.get_digest(realm or get_realm()) == hash_password(
            password)

    def generate_password (self, realm=None, length=6):
        """Set the password to a random value and return the new password."""
        password = binascii.b2a_base64(binascii.unhexlify(randbytes(length)))
        password = password[:length]
        self.set_password(password, realm=realm)
        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 format_realname(self):
        return ''

    def is_null(self):
        return not self

    def is_disabled(self, realm=None):
        return not bool(self.get_digester().get_digest(realm or get_realm()))

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

    def is_admin(self):
        return self.is_granted('staff')

    def is_granted(self, permission, granter=True):
        return self.get_permissions().is_granted(permission, granter)

    def can_manage_permissions(self):
        return self.is_granted('manage-permissions')

    def get_email_realname_pair(self):
        return self.get_email(), self.format_realname() or ''

add_getters_and_setters(DulcineaUser)


class DulcineaUserDatabase (Persistent):
    """
    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.
    """

    motd_is = spec(
       string,
       "message-of-the-day")

    user_class = DulcineaUser

    users_is = mapping({string:user_class})

    def __init__(self):
        self.users = BTree()
        self.users[''] = self.create_user('')
        self.motd = ''

    def create_user(self, user_id):
        return self.user_class(user_id)

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

    def __iter__(self):
        return self.users.itervalues()

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

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

    def get_matching_user(self, identifier):
        """(identifier : string) -> DulcineaUser | None

        Return a user with matching id or email address, or None
        if no such user is found.
        """
        user = self.get_user(identifier)
        if user:
            return user
        elif '@' in identifier:
            identifier = identifier.lower()
            for user in self.users.itervalues():
                if (user.get_email() or '').lower() == identifier:
                    return user
        return None

    def get(self, user_id, default=None):
        return self.users.get(user_id, default=default)

    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 get(self, value, default=None):
        return self.users.get(value, default=default)

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

    def add_user(self, user):
        """Add User object 'user' to the user database.
        """
        assert not self.users.has_key(user.get_id())
        self.users[user.id] = user

    def gen_users_granted(self, permission, granter=True):
        for user in self.users.itervalues():
            if user.is_granted(permission, granter):
                yield user

    def set_motd(self, value):
        specify(self, motd=value)

add_getters(DulcineaUserDatabase)

