Logo Search packages:      
Sourcecode: zope-loginmanager version File versions

UserSources.py

from AccessControl.User import BasicUser
from AccessControl.PermissionRole import _what_not_even_god_should_do
from OFS.SimpleItem import Item
from OFS.Folder import Folder
from Globals import HTMLFile, MessageDialog
from Products.ZPatterns.Rack import Rack
from Products.ZPatterns.DataSkins import DataSkin,_ZClass_for_DataSkin
from Products.PlugIns import defaultConstructors, MakePICBase
from LoginManager import BetterLocalRolesMixin
from AccessControl import getSecurityManager
from string import split
import ZODB.bpthread
import sys, types, sha, whrandom, string, binascii, time

try:
    import crypt
    saltdict = './' + string.digits + string.uppercase + string.lowercase
except:
    crypt = None

# dictionary, indexed by oid of UserSource, of dictionaries, indexed by
# user name, of dictionaries indexed by 'roles', 'domains', ...

_marker = []
_cache = {}
_cache_lock = ZODB.bpthread.allocate_lock()
















# Object used to push/pop a user on the security context stack (private use only)
class dummyContext:
    def __init__(self,user): self.user = user
    def getOwner(self): return self.user


def setuid(REQUEST,user):
    """Push a user on the security context stack, returning a value which can be passed back in to restore the previous state"""
    sm = getSecurityManager()

    if type(user) is type(()):
        dc,user = user
        sm.removeContext(dc)
    else:
        dc = dummyContext(user)
        sm.addContext(dc)
            
    sc = sm._context  # XXX hack!!!
    user, sc.user = sc.user, user   # swap users
    return dc, user





















00084 class LoginUser(DataSkin, BetterLocalRolesMixin, BasicUser, Item):

    """User that can login by way of a LoginManager.  Delegates actual
    behaviors to the UserSource."""

    def getUserName(self):
        return self.id

    getId = getUserName
    
    def getRoles(self):
        return self.__dict__['_v_dm_'].rolesForUser(self)

    def getDomains(self):
        return self.__dict__['_v_dm_'].domainsForUser(self)

    def authenticate(self, password, request):
        return self.__dict__['_v_dm_'].authenticateUser(self, password, request)

00103     def logout(self):
        """Log out - can be called as AUTHENTICATED_USER.logout()
        Will only take effect if user has same getUserName() as
        AUTHENTICATED_USER."""
        return self.__dict__['_v_dm_'].logoutUser(self.getUserName())

    def credentialsChanged(self,credentials):
        self.cacheClear()
        self.__dict__['_v_dm_'].credentialsChanged(self,self.getUserName(),credentials)

    def cacheClear(self):
        self.__dict__['_v_dm_'].cacheClear(self.getUserName())

    # Class Metadata
    
    isALoginUser = 1

    meta_type = 'Login User'
    icon = 'misc_/LoginManager/user'



00125 class BasicUserSource(Rack):

    """A way of getting user objects, and implementing their innards"""

    _defaultClass = LoginUser    # make objects this class unless ZClass spec'd

    def rolesForUser(self,user):    pass
    def domainsForUser(self,user):  pass
    def authenticateUser(self,user,password,request):
            pass

    # Note: credentialsChanged and logoutUser methods are
    # required, but the default implementation is to
    # acquire these from the LoginManager where the
    # UserSource resides.  The interface for these methods
    # is defined in the LoginManager class.

    def __init__(self, id, title):
        Rack.__init__.im_func(self,id,title)
        self._role_mappings = {}

    # Cache support

    def cacheClear(self, name=None):
        global _cache, _cache_lock

        _cache_lock.acquire()
        try:
            if name:
                try:
                    del _cache[self._p_oid][name]
                except KeyError:
                    pass
            else:
                _cache[self._p_oid] = {}
        finally:
            _cache_lock.release()




    cacheSet__roles__ = _what_not_even_god_should_do

00168     def cacheSet(self, name, key, value):
        """Set cached data for user 'name'"""
        global _cache, _cache_lock

        _cache_lock.acquire()
        try:
            _cache[self._p_oid] = usc = _cache.get(self._p_oid, {})
            d = {}
            usc[name] = uc = usc.get(name, d)
            if uc is d:
                d['time'] = time.time()
                
            uc[key] = value

        finally:
            _cache_lock.release()

00185     def cacheGet(self, name, key, default=None):
        """Get cached data for user 'name'"""
        global _cache #, _cache_lock

        r = default
        # _cache_lock.acquire()
        try:
            try:
                d = _cache[self._p_oid][name]
                if time.time() > (d['time'] + self.cache_expire):
                    del _cache[self._p_oid][name]
                    return default
                r = d.get(key, default)
            except KeyError:
                pass
        finally:
            pass # _cache_lock.release()
        return r




00207     def cacheGetAuth(self, name, password):
        """False doesn't mean 'no', it just means we don't know 'yes'"""
        p = self.cacheGet(name, 'pass')
        if p and (sha.sha(password).digest() == p):
            return 1
        return 0

    cacheSetAuth__roles__ = _what_not_even_god_should_do
    
    def cacheSetAuth(self, name, password):
        self.cacheSet(name, 'pass', sha.sha(password).digest())






























    # Role Mapping Support

    def _map_roles(self, roles):
        # Apply role mappings

        rd = {}

        for r in roles:
            rd[r] = 1

        for r in rd.keys():
            for mr in self._role_mappings.get(r, []):
                rd[mr] = 1

        return tuple(rd.keys())

    manage_mappingsForm = HTMLFile('www/mappings', globals())

00266     def role_mappings_list(self):
        """Get role mappings as from,to pairs"""
        l = []
        for k, v in self._role_mappings.items():
            for x in v:
                l.append((k, x))
        l.sort()
        return l















00289     def manage_mappings(self, mapping=[], REQUEST=None):
        """manage role mappings"""
        self._role_mappings = rm = {}
        for m in mapping:
            if m.mfrom and m.mto:
                if rm.has_key(m.mfrom):
                    rm[m.mfrom].append(m.mto)
                else:
                    rm[m.mfrom] = [m.mto]

        # XXX probably a better way to do this...
        if not filter(lambda x: x['id'] == 'cache_expire', self._properties):
            self._properties = (
                {'id':'cache_expire', 'type': 'int', 'mode': 'w'},
            ) + self._properties

        self.cacheClear()

        if REQUEST: return self.manage_main(
            self,REQUEST,update_menu=1,
            manage_tabs_message="Role mappings changed.")

    def _zclassOK(self, z):
        return hasattr(z._zclass_, 'isALoginUser')

















    # Auth helper functions

00332     def encodePassword(self, text, scheme='CRYPT', salt=None):
        """Encode a password"""
        if scheme == 'CRYPT' and crypt is not None:
            if salt is None:
                salt = whrandom.randint(0, 2**12-1)
                salt = saltdict[salt >> 6] + saltdict[salt & 0x3f]
            pw = '{CRYPT}' + crypt.crypt(text, salt)
        elif scheme == 'SHA':
            pw = '{SHA}' + binascii.b2a_base64(sha.sha(text).digest())[:-1]
        elif scheme == 'SSHA':
            if salt is None:
                # XXX random salt selection could be much better...
                salt = hex(whrandom.randint(0, sys.maxint-1))[2:]
                salt = sha.sha(salt).digest()
            ssha = sha.sha(text + salt).digest()+salt
            pw = '{SSHA}' + binascii.b2a_base64(ssha)[:-1]
        else:
            pw = text

        return pw

00353     def extractSalt(self, pw):
        """Extract the salt from an encrypted password"""

        if pw[:7] == '{CRYPT}':
            return pw[7:9]
        elif pw[:6] == '{SSHA}':
            return binascii.a2b_base64(pw[6:])[20:]
        else:
            return None









00371     def comparePassword(self, text, pw):
        """Check if password matches"""

        salt = self.extractSalt(pw)
        if pw[0] == '{':
            scheme = split(pw[1:], '}', 1)[0]
        else:
            scheme = ''
        text = self.encodePassword(text, scheme, salt)
        return (text == pw)

    # Class Metadata

    manage_options_left = Rack.manage_options_left + (
            {'label':'Role Mappings',   'action':'manage_mappingsForm'},
    )

    meta_type = "Basic User Source"
    __plugin_kind__ = "User Source"
    icon = 'misc_/LoginManager/usersource'
    cache_expire = 900

    _properties=(
        {'id':'title', 'type': 'string', 'mode': 'w'},
        {'id':'cache_expire', 'type': 'int', 'mode': 'w'},
    )
                        


MakePICBase(BasicUserSource)











class UserSource(BasicUserSource):

    meta_type = 'User Source'

    def rolesForUser(self, user):
        name = user.getUserName()

        data = self.cacheGet(name, 'roles', _marker)

        if data is not _marker:
            return data

        data = self._map_roles(getattr(user, 'roles', []))

        self.cacheSet(name, 'roles', data)

        return data

    def domainsForUser(self, user):
        name = user.getUserName()

        data = self.cacheGet(name, 'domains', _marker)
        if data is not _marker:
            return data

        data = getattr(user, 'domains', ())

        self.cacheSet(name, 'domains', data)

        return data











    def authenticateUser(self,user,password,request):

        name = user.getUserName()

        if self.cacheGetAuth(name, password):
            return 1

        if hasattr(self, 'userAuthenticate'):
            old_au = setuid(self.REQUEST, _LoggingInUser)
            try:
                ok = self.userAuthenticate(
                    self, request, user=user, password=password)
            finally:
                setuid(self.REQUEST, old_au)
        else:
            encpw = getattr(user, '__', '\0')
            ok = self.comparePassword(password, encpw)
        
        if ok:
            self.cacheSetAuth(name, password)

        return ok



MakePICBase(UserSource)















from LoginManager import _LoggingInUser
from AccessControl.User import nobody

00497 class GenericUserSource(BasicUserSource):

    """A GUF-alike user source"""

    meta_type = "Generic User Source"

    def retrieveItem(self,name):

        exist = self.cacheGet(name, 'exist', 0)

        old_au = setuid(self.REQUEST, _LoggingInUser)

        if not exist and hasattr(self, 'userExists'):
            if self.userExists(self, self.REQUEST, username=name) == 1:
                exist = 1
                self.cacheSet(name, 'exist', 1)

        setuid(self.REQUEST, old_au)

        if exist:
            return self._RawItem(name)

        return None















    def rolesForUser(self,user):

        name = user.getUserName()

        data = self.cacheGet(name, 'roles', _marker)
        if data is not _marker:
            return data

        old_au = setuid(self.REQUEST, _LoggingInUser)

        try:
            data = self.userRoles(self, self.REQUEST, username=name)
            if type(data) == types.StringType:
                data = split(data)
            else:
                data = list(data)
                if len(data) > 0 and type(data[0]) != types.StringType:
                    if hasattr(data[0], 'role'):
                        data = map(lambda x: x.role, data)
                    elif hasattr(data[0], 'r'):
                        data = map(lambda x: x.r, data)
        except:
            data = []
            # XXX log exception or something

        setuid(self.REQUEST, old_au)

        data = self._map_roles(data)

        self.cacheSet(name, 'roles', data)

        return data









    def domainsForUser(self,user):

        name = user.getUserName()

        data = self.cacheGet(name, 'domains', _marker)
        if data is not _marker:
            return data

        old_au = setuid(self.REQUEST, _LoggingInUser)

        try:
            data = self.userDomains(self, self.REQUEST, username=name)
            if type(data) == types.StringType:
                data = split(data)
            else:
                data = list(data)
                if len(data) > 0 and type(data[0]) != types.StringType:
                    if hasattr(data[0], 'domain'):
                        data = map(lambda x: x.domain, data)
                    elif hasattr(data[0], 'd'):
                        data = map(lambda x: x.d, data)
        except:
            data = ()
            # XXX log exception or something

        setuid(self.REQUEST, old_au)
        self.cacheSet(name, 'domains', tuple(data))

        return data












    def authenticateUser(self,user,password,request):

        name = user.getUserName()

        if self.cacheGetAuth(name, password):
            return 1

        if not hasattr(self, 'userAuthenticate'):
            return 0

        old_au = setuid(self.REQUEST, _LoggingInUser)
        try:
            ok = self.userAuthenticate(
                self, request, username=name, password=password)

        finally:
            setuid(self.REQUEST, old_au)

        if ok:
            self.cacheSetAuth(name, password)

        return ok


class _ZClass_for_LoginUser(_ZClass_for_DataSkin):
    _zclass_ = LoginUser















def initialize(context):

    context.registerPlugInClass(
        UserSource,
        permission = 'Add User Source',
        constructors = defaultConstructors(UserSource, globals()),
    )

    context.registerPlugInClass(
        GenericUserSource,
        permission = 'Add Generic User Source',
        constructors = defaultConstructors(GenericUserSource, globals()),
                       
    )

    context.registerPIContainerBase(BasicUserSource) # XXX?
    context.registerPIContainerBase(UserSource)
    context.registerZClass(_ZClass_for_LoginUser)

Generated by  Doxygen 1.6.0   Back to index