#!/usr/bin/env python
#
# userdbm.py:		OO-user database with authentication
#
# Author:		Christopher Arndt <chris.arndt@web.de>
# Version:		1.1
# Date:			26.04.2002
# Copyright:		LGPL
"""OO-user database using dbm password files.
"""

__all__ = ['UserDB']

__author__  = "Christopher Arndt <chris.arndt@web.de>"
__version__ = "1.0b"

import anydbm, cPickle
from getpass import getpass
import authlib

class UserDB:
    """Represents a user database and provides authentication methods.
    """
    
    def __init__(self, dbname, crypt_type='md5', failsafe=0):
        self.dbname = dbname
        self.crypt_type = crypt_type
        self.failsafe = failsafe
        self.user_prompt = 'Login: '
        self.pass_prompt = 'Password: '

    # database query methods
    def get_user(self, user):
        """Return database entry for the given user name.
        
        Returns a tuple (encrypted password, extra info).
        """

        try:
            db = anydbm.open(self.dbname)
        except anydbm.error:
            if self.failsafe:
                raise KeyError, "User '%s' does not exists." % user
            raise IOError, "Could not open database file '%s'" % self.dbname

        entry = db[user].split(':', 1)
        try:
            info = cPickle.loads(entry[1])
        except IndexError:
            info = {}
        db.close()
        return entry[0], info

    def get_passwd(self, user):
        """Return encrypted password for the given user.
        """

        return self.get_user(user)[0]

    def get_info(self, user):
        """Return extra info for the given user as a dictionary.
        """

        return self.get_user(user)[1]

    # pseudo dictionary methods
    __getitem__ = get_user

    def keys(self):
        
        try:
            db = anydbm.open(self.dbname)
        except anydbm.error:
            if self.failsafe:
                return []
            raise IOError, "Could not open database file '%s'" % self.dbname

        keys = db.keys()
        db.close()
        return keys
        
    def has_key(self, key):
        try:
            db = anydbm.open(self.dbname)
        except anydbm.error:
            if self.failsafe:
                return 0
            raise IOError, "Could not open database file '%s'" % self.dbname

        ret = db.has_key(key)
        db.close()
        return ret

    # database manipulation methods
    def add_user(self, user, passwd=None, **args):
        """Add a new user to the database.
        """

        try:
            db = anydbm.open(self.dbname, 'w')
        except anydbm.error:
            import sys
            sys.stderr.write("Notice: creating new database!\n")
            db = anydbm.open(self.dbname, 'c')

        if db.has_key(user):
            db.close()
            raise ValueError, "User '%s' already exists." % user
        
        if passwd == None:
            passwd = '!!'
        elif passwd != '':
            passwd = authlib.passcrypt(passwd, method=self.crypt_type)
        
        if args:
            info = ":" + cPickle.dumps(args, 1)
        else:
            info = ''

        db[user] = passwd + info
        db.close()

    def del_user(self, user):
        """Delete a user from the database.
        """

        try:
            db = anydbm.open(self.dbname, 'w')
        except anydbm.error:
            if self.failsafe:
                return
            raise IOError, "Could not open database file '%s'" % self.dbname
        
        try:
            del db[user]
        except KeyError:
            db.close()
            raise KeyError, "User '%s' does not exist." % (user)

    def set_passwd(self, user, passwd=None):
        """Change the passwd of a user.
        
        Setting password to an empty string deletes the password,
        setting it to None disables login.
        """
        
        try:
            db = anydbm.open(self.dbname, 'w')
        except anydbm.error:
            if self.failsafe:
                return
            raise IOError, "Could not open database file '%s'" % self.dbname

        if not db.has_key(user):
            db.close()
            raise KeyError, "User '%s' does not exists." % user
        
        if passwd == None:
            passwd = '!!'
        elif passwd != '':
            passwd = authlib.passcrypt(passwd, method=self.crypt_type)
        
        try:
            info = ":" + db[user].split(':', 1)[1]
        except IndexError:
            info = ''
        
        db[user] = passwd + info
        db.close()

    def set_info(self, user, **args):
        """Set extra info for user to given keyword args.
        """

        try:
            db = anydbm.open(self.dbname, 'w')
        except anydbm.error:
            if self.failsafe:
                return
            raise IOError, "Could not open database file '%s'" % self.dbname

        if not db.has_key(user):
            db.close()
            raise KeyError, "User '%s' does not exists." % user

        passwd = self.get_passwd(user)
        if args:
            info = ":" + cPickle.dumps(args, 1)
        else:
            _info = ''

        db[user] = passwd + info
        db.close()

    def update_info(self, user, **args):
        """Updates extra info for user with given keyword args.
        """

        info = self.get_info(user)
        info.update(args)
        apply(self.set_info, [user], info)

    # authentication methods
    def check_passwd(self, user, passwd):
        """Validate given user, passwd pair against database.
        """

        return authlib.check_passwd(user, passwd, self)

    def login(self, user=None, user_prompt=None, pass_prompt=None, 
      max_tries=3):
        """Generate a login screen and validate the login.
        
        Returns user name or None on failure.
        """

        return authlib.login(user, self, user_prompt, pass_prompt, max_tries)

if __name__ == '__main__':
    # test suite
    import sys
    if len(sys.argv) > 1:
        dbname = sys.argv[1]
    else:
        dbname = 'test.db' 
    db = UserDB(dbname)
    user = db.login(max_tries=1)
    if user:
        print "Access granted"
        print "Info: " + repr(db.get_info(user))
    else:
        print "Access denied"