"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/dulcinea/lib/user.py $
$Id: user.py 27903 2006-01-30 16:07:27Z dbinger $
"""
from dulcinea.sort import lexical_sort
from dulcinea.common import get_site_name
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()


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, 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.remove_digest(get_realm())
        else:
            digester.set_digest(get_realm(), hash_password(new_password))

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

    def generate_password (self, 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)
        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 self.id == ''

    def is_disabled(self):
        return not bool(self.get_digester().get_digest(get_realm()))

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

    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_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.
        """
        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)


def revise_users(connection):
    """
    For update_db scripts that revise DulcineaUsers from 0.12 to 0.13.
    You may need to edit this if your users are in other places.
    """
    from qp.pub.user import Digester, TokenSet, Permissions
    from qp.lib.spec import specify, get_spec_problems, match
    globals()['Permissions'] = Permissions
    user_db = connection.get_root()['user_db']
    for user in user_db.get_users().itervalues():
        specify(user, digester=Digester(), tokens=TokenSet())
        user.permissions._p_note_change()
        if user.password_hash:
            user.get_digester().set_digest(get_realm(), user.password_hash)
        del user.password_hash
        if not match(user.get_email(), user.email_is):
            user.email = user.email.strip().replace(',', '.').replace(';', '.')
            if ('@' in user.email and '.' not in user.email):
                user.email = user.email + '.com'
            if match(user.get_email(), user.email_is):
                print 'Fixed email address:', user.get_email()
            else:
                print
                print 'Removing bogus email address:', user.get_email()
                user.email = None
        problems = get_spec_problems(user)
        if problems:
            print
            print '\n'.join(problems)

