Back to index

plone3  3.1.7
ZODBUserManager.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights
00004 # Reserved.
00005 #
00006 # This software is subject to the provisions of the Zope Public License,
00007 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this
00008 # distribution.
00009 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00010 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00011 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00012 # FOR A PARTICULAR PURPOSE.
00013 #
00014 ##############################################################################
00015 """ Classes: ZODBUserManager
00016 
00017 $Id: ZODBUserManager.py 83599 2008-02-06 18:20:16Z tseaver $
00018 """
00019 import sha
00020 import copy
00021 
00022 from AccessControl import ClassSecurityInfo, AuthEncoding
00023 from AccessControl.SecurityManagement import getSecurityManager
00024 from App.class_init import default__class_init__ as InitializeClass
00025 from BTrees.OOBTree import OOBTree
00026 from OFS.Cache import Cacheable
00027 
00028 from zope.interface import Interface
00029 
00030 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00031 
00032 from Products.PluggableAuthService.interfaces.plugins \
00033     import IAuthenticationPlugin
00034 from Products.PluggableAuthService.interfaces.plugins \
00035     import IUserEnumerationPlugin
00036 from Products.PluggableAuthService.interfaces.plugins \
00037     import IUserAdderPlugin
00038 
00039 from Products.PluggableAuthService.permissions import ManageUsers
00040 from Products.PluggableAuthService.permissions import SetOwnPassword
00041 from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
00042 from Products.PluggableAuthService.utils import classImplements
00043 from Products.PluggableAuthService.utils import createViewName
00044 from Products.PluggableAuthService.utils import postonly
00045 
00046 class IZODBUserManager(Interface):
00047     """ Marker interface.
00048     """
00049 
00050 
00051 manage_addZODBUserManagerForm = PageTemplateFile(
00052     'www/zuAdd', globals(), __name__='manage_addZODBUserManagerForm' )
00053 
00054 def addZODBUserManager( dispatcher, id, title=None, REQUEST=None ):
00055     """ Add a ZODBUserManagern to a Pluggable Auth Service. """
00056 
00057     zum = ZODBUserManager(id, title)
00058     dispatcher._setObject(zum.getId(), zum)
00059 
00060     if REQUEST is not None:
00061         REQUEST['RESPONSE'].redirect(
00062                                 '%s/manage_workspace'
00063                                 '?manage_tabs_message='
00064                                 'ZODBUserManager+added.'
00065                             % dispatcher.absolute_url())
00066 
00067 class ZODBUserManager( BasePlugin, Cacheable ):
00068 
00069     """ PAS plugin for managing users in the ZODB.
00070     """
00071 
00072     meta_type = 'ZODB User Manager'
00073 
00074     security = ClassSecurityInfo()
00075 
00076     def __init__(self, id, title=None):
00077 
00078         self._id = self.id = id
00079         self.title = title
00080 
00081         self._user_passwords = OOBTree()
00082         self._login_to_userid = OOBTree()
00083         self._userid_to_login = OOBTree()
00084 
00085     #
00086     #   IAuthenticationPlugin implementation
00087     #
00088     security.declarePrivate( 'authenticateCredentials' )
00089     def authenticateCredentials( self, credentials ):
00090 
00091         """ See IAuthenticationPlugin.
00092 
00093         o We expect the credentials to be those returned by
00094           ILoginPasswordExtractionPlugin.
00095         """
00096         login = credentials.get( 'login' )
00097         password = credentials.get( 'password' )
00098 
00099         if login is None or password is None:
00100             return None
00101 
00102         userid = self._login_to_userid.get( login, login )
00103 
00104         reference = self._user_passwords.get(userid)
00105 
00106         if reference is None:
00107             return None
00108         
00109         if AuthEncoding.is_encrypted( reference ):
00110             if AuthEncoding.pw_validate( reference, password ):
00111                 return userid, login
00112 
00113         # Support previous naive behavior
00114         digested = sha.sha( password ).hexdigest()
00115 
00116         if reference == digested:
00117             return userid, login
00118 
00119         return None
00120 
00121     #
00122     #   IUserEnumerationPlugin implementation
00123     #
00124     security.declarePrivate( 'enumerateUsers' )
00125     def enumerateUsers( self
00126                       , id=None
00127                       , login=None
00128                       , exact_match=False
00129                       , sort_by=None
00130                       , max_results=None
00131                       , **kw
00132                       ):
00133 
00134         """ See IUserEnumerationPlugin.
00135         """
00136         user_info = []
00137         user_ids = []
00138         plugin_id = self.getId()
00139         view_name = createViewName('enumerateUsers', id or login)
00140 
00141 
00142         if isinstance( id, basestring ):
00143             id = [ id ]
00144 
00145         if isinstance( login, basestring ):
00146             login = [ login ]
00147 
00148         # Look in the cache first...
00149         keywords = copy.deepcopy(kw)
00150         keywords.update( { 'id' : id
00151                          , 'login' : login
00152                          , 'exact_match' : exact_match
00153                          , 'sort_by' : sort_by
00154                          , 'max_results' : max_results
00155                          }
00156                        )
00157         cached_info = self.ZCacheable_get( view_name=view_name
00158                                          , keywords=keywords
00159                                          , default=None
00160                                          )
00161         if cached_info is not None:
00162             return tuple(cached_info)
00163 
00164         terms = id or login
00165 
00166         if exact_match:
00167             if terms:
00168 
00169                 if id:
00170                     # if we're doing an exact match based on id, it
00171                     # absolutely will have been qualified (if we have a
00172                     # prefix), so we can ignore any that don't begin with
00173                     # our prefix
00174                     id = [ x for x in id if x.startswith(self.prefix) ]
00175                     user_ids.extend( [ x[len(self.prefix):] for x in id ] )
00176                 elif login:
00177                     user_ids.extend( [ self._login_to_userid.get( x )
00178                                        for x in login ] )
00179 
00180                 # we're claiming an exact match search, if we still don't
00181                 # have anything, better bail.
00182                 if not user_ids:
00183                     return ()
00184             else:
00185                 # insane - exact match with neither login nor id
00186                 return ()
00187 
00188         if user_ids:
00189             user_filter = None
00190 
00191         else:   # Searching
00192             user_ids = self.listUserIds()
00193             user_filter = _ZODBUserFilter( id, login, **kw )
00194 
00195         for user_id in user_ids:
00196 
00197             if self._userid_to_login.get( user_id ):
00198                 e_url = '%s/manage_users' % self.getId()
00199                 qs = 'user_id=%s' % user_id
00200 
00201                 info = { 'id' : self.prefix + user_id
00202                        , 'login' : self._userid_to_login[ user_id ]
00203                        , 'pluginid' : plugin_id
00204                        , 'editurl' : '%s?%s' % (e_url, qs)
00205                        } 
00206 
00207                 if not user_filter or user_filter( info ):
00208                     user_info.append( info )
00209 
00210         # Put the computed value into the cache
00211         self.ZCacheable_set(user_info, view_name=view_name, keywords=keywords)
00212 
00213         return tuple( user_info )
00214 
00215     #
00216     #   IUserAdderPlugin implementation
00217     #
00218     security.declarePrivate( 'doAddUser' )
00219     def doAddUser( self, login, password ):
00220         try:
00221             self.addUser( login, login, password )
00222         except KeyError:
00223             return False
00224         return True
00225 
00226     #
00227     #   (notional)IZODBUserManager interface
00228     #
00229     security.declareProtected( ManageUsers, 'listUserIds' )
00230     def listUserIds( self ):
00231 
00232         """ -> ( user_id_1, ... user_id_n )
00233         """
00234         return self._user_passwords.keys()
00235 
00236     security.declareProtected( ManageUsers, 'getUserInfo' )
00237     def getUserInfo( self, user_id ):
00238 
00239         """ user_id -> {}
00240         """
00241         return { 'user_id' : user_id
00242                , 'login_name' : self._userid_to_login[ user_id ]
00243                , 'pluginid' : self.getId()
00244                }
00245 
00246     security.declareProtected( ManageUsers, 'listUserInfo' )
00247     def listUserInfo( self ):
00248 
00249         """ -> ( {}, ...{} )
00250 
00251         o Return one mapping per user, with the following keys:
00252 
00253           - 'user_id' 
00254           - 'login_name'
00255         """
00256         return [ self.getUserInfo( x ) for x in self._user_passwords.keys() ]
00257 
00258     security.declareProtected( ManageUsers, 'getUserIdForLogin' )
00259     def getUserIdForLogin( self, login_name ):
00260 
00261         """ login_name -> user_id
00262 
00263         o Raise KeyError if no user exists for the login name.
00264         """
00265         return self._login_to_userid[ login_name ]
00266 
00267     security.declareProtected( ManageUsers, 'getLoginForUserId' )
00268     def getLoginForUserId( self, user_id ):
00269 
00270         """ user_id -> login_name
00271 
00272         o Raise KeyError if no user exists for that ID.
00273         """
00274         return self._userid_to_login[ user_id ]
00275 
00276     security.declarePrivate( 'addUser' )
00277     def addUser( self, user_id, login_name, password ):
00278 
00279         if self._user_passwords.get( user_id ) is not None:
00280             raise KeyError, 'Duplicate user ID: %s' % user_id
00281 
00282         if self._login_to_userid.get( login_name ) is not None:
00283             raise KeyError, 'Duplicate login name: %s' % login_name
00284 
00285         self._user_passwords[ user_id ] = self._pw_encrypt( password)
00286         self._login_to_userid[ login_name ] = user_id
00287         self._userid_to_login[ user_id ] = login_name
00288 
00289         # enumerateUsers return value has changed
00290         view_name = createViewName('enumerateUsers')
00291         self.ZCacheable_invalidate(view_name=view_name)
00292 
00293     security.declarePrivate('updateUser')
00294     def updateUser(self, user_id, login_name):
00295 
00296         # The following raises a KeyError if the user_id is invalid
00297         old_login = self.getLoginForUserId(user_id)
00298 
00299         del self._login_to_userid[old_login]
00300         self._login_to_userid[login_name] = user_id
00301         self._userid_to_login[user_id] = login_name
00302 
00303     security.declarePrivate( 'removeUser' )
00304     def removeUser( self, user_id ):
00305 
00306         if self._user_passwords.get( user_id ) is None:
00307             raise KeyError, 'Invalid user ID: %s' % user_id
00308 
00309         login_name = self._userid_to_login[ user_id ]
00310 
00311         del self._user_passwords[ user_id ]
00312         del self._login_to_userid[ login_name ]
00313         del self._userid_to_login[ user_id ]
00314 
00315         # Also, remove from the cache
00316         view_name = createViewName('enumerateUsers')
00317         self.ZCacheable_invalidate(view_name=view_name)
00318         view_name = createViewName('enumerateUsers', user_id)
00319         self.ZCacheable_invalidate(view_name=view_name)
00320 
00321     security.declarePrivate( 'updateUserPassword' )
00322     def updateUserPassword( self, user_id, password ):
00323 
00324         if self._user_passwords.get( user_id ) is None:
00325             raise KeyError, 'Invalid user ID: %s' % user_id
00326 
00327         if password:
00328             self._user_passwords[ user_id ] = self._pw_encrypt( password )
00329 
00330     security.declarePrivate( '_pw_encrypt' )
00331     def _pw_encrypt( self, password ):
00332         """Returns the AuthEncoding encrypted password
00333 
00334         If 'password' is already encrypted, it is returned
00335         as is and not encrypted again.
00336         """
00337         if AuthEncoding.is_encrypted( password ):
00338             return password
00339         return AuthEncoding.pw_encrypt( password )
00340 
00341     #
00342     #   ZMI
00343     #
00344     manage_options = ( ( { 'label': 'Users', 
00345                            'action': 'manage_users', }
00346                          ,
00347                        )
00348                      + BasePlugin.manage_options
00349                      + Cacheable.manage_options
00350                      )
00351 
00352     security.declarePublic( 'manage_widgets' )
00353     manage_widgets = PageTemplateFile( 'www/zuWidgets'
00354                                      , globals()
00355                                      , __name__='manage_widgets'
00356                                      )
00357 
00358     security.declareProtected( ManageUsers, 'manage_users' )
00359     manage_users = PageTemplateFile( 'www/zuUsers'
00360                                    , globals()
00361                                    , __name__='manage_users'
00362                                    )
00363 
00364     security.declareProtected( ManageUsers, 'manage_addUser' )
00365     def manage_addUser( self
00366                       , user_id
00367                       , login_name
00368                       , password
00369                       , confirm
00370                       , RESPONSE=None
00371                       ):
00372         """ Add a user via the ZMI.
00373         """
00374         if password != confirm:
00375             message = 'password+and+confirm+do+not+match'
00376 
00377         else:
00378         
00379             if not login_name:
00380                 login_name = user_id
00381 
00382             # XXX:  validate 'user_id', 'login_name' against policies?
00383 
00384             self.addUser( user_id, login_name, password )
00385 
00386             message = 'User+added'
00387 
00388         if RESPONSE is not None:
00389             RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
00390                              % ( self.absolute_url(), message )
00391                              )
00392 
00393     security.declareProtected( ManageUsers, 'manage_updateUserPassword' )
00394     def manage_updateUserPassword( self
00395                                  , user_id
00396                                  , password
00397                                  , confirm
00398                                  , RESPONSE=None
00399                                  , REQUEST=None
00400                                  ):
00401         """ Update a user's login name / password via the ZMI.
00402         """
00403         if password and password != confirm:
00404             message = 'password+and+confirm+do+not+match'
00405 
00406         else:
00407         
00408             self.updateUserPassword( user_id, password )
00409 
00410             message = 'password+updated'
00411 
00412         if RESPONSE is not None:
00413             RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
00414                              % ( self.absolute_url(), message )
00415                              )
00416     manage_updateUserPassword = postonly(manage_updateUserPassword)
00417 
00418     security.declareProtected( ManageUsers, 'manage_updateUser' )
00419     def manage_updateUser(self, user_id, login_name, RESPONSE=None):
00420         """ Update a user's login name via the ZMI.
00421         """
00422         if not login_name:
00423             login_name = user_id
00424 
00425         # XXX:  validate 'user_id', 'login_name' against policies?
00426 
00427         self.updateUser(user_id, login_name)
00428 
00429         message = 'Login+name+updated'
00430 
00431         if RESPONSE is not None:
00432             RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
00433                              % ( self.absolute_url(), message )
00434                              )
00435 
00436     security.declareProtected( ManageUsers, 'manage_removeUsers' )
00437     def manage_removeUsers( self
00438                           , user_ids
00439                           , RESPONSE=None
00440                           , REQUEST=None
00441                           ):
00442         """ Remove one or more users via the ZMI.
00443         """
00444         user_ids = filter( None, user_ids )
00445 
00446         if not user_ids:
00447             message = 'no+users+selected'
00448 
00449         else:
00450         
00451             for user_id in user_ids:
00452                 self.removeUser( user_id )
00453 
00454             message = 'Users+removed'
00455 
00456         if RESPONSE is not None:
00457             RESPONSE.redirect( '%s/manage_users?manage_tabs_message=%s'
00458                              % ( self.absolute_url(), message )
00459                              )
00460     manage_removeUsers = postonly(manage_removeUsers)
00461 
00462     #
00463     #   Allow users to change their own login name and password.
00464     #
00465     security.declareProtected( SetOwnPassword, 'getOwnUserInfo' )
00466     def getOwnUserInfo( self ):
00467 
00468         """ Return current user's info.
00469         """
00470         user_id = getSecurityManager().getUser().getId()
00471 
00472         return self.getUserInfo( user_id )
00473 
00474     security.declareProtected( SetOwnPassword, 'manage_updatePasswordForm' )
00475     manage_updatePasswordForm = PageTemplateFile( 'www/zuPasswd'
00476                                    , globals()
00477                                    , __name__='manage_updatePasswordForm'
00478                                    )
00479 
00480     security.declareProtected( SetOwnPassword, 'manage_updatePassword' )
00481     def manage_updatePassword( self
00482                              , login_name
00483                              , password
00484                              , confirm
00485                              , RESPONSE=None
00486                              , REQUEST=None
00487                              ):
00488         """ Update the current user's password and login name.
00489         """
00490         user_id = getSecurityManager().getUser().getId()
00491         if password != confirm:
00492             message = 'password+and+confirm+do+not+match'
00493 
00494         else:
00495         
00496             if not login_name:
00497                 login_name = user_id
00498 
00499             # XXX:  validate 'user_id', 'login_name' against policies?
00500             self.updateUser( user_id, login_name )
00501             self.updateUserPassword( user_id, password )
00502 
00503             message = 'password+updated'
00504 
00505         if RESPONSE is not None:
00506             RESPONSE.redirect( '%s/manage_updatePasswordForm'
00507                                '?manage_tabs_message=%s'
00508                              % ( self.absolute_url(), message )
00509                              )
00510     manage_updatePassword = postonly(manage_updatePassword)
00511 
00512 classImplements( ZODBUserManager
00513                , IZODBUserManager
00514                , IAuthenticationPlugin
00515                , IUserEnumerationPlugin
00516                , IUserAdderPlugin
00517                )
00518 
00519 InitializeClass( ZODBUserManager )
00520 
00521 class _ZODBUserFilter:
00522 
00523     def __init__( self
00524                 , id=None
00525                 , login=None
00526                 , **kw
00527                 ):
00528 
00529         self._filter_ids = id
00530         self._filter_logins = login
00531         self._filter_keywords = kw
00532 
00533     def __call__( self, user_info ):
00534 
00535         if self._filter_ids:
00536 
00537             key = 'id'
00538             to_test = self._filter_ids
00539 
00540         elif self._filter_logins:
00541 
00542             key = 'login'
00543             to_test = self._filter_logins
00544 
00545         elif self._filter_keywords:
00546             return 0    # TODO:  try using 'kw'
00547 
00548         else:
00549             return 1    # the search is done without any criteria
00550 
00551         value = user_info.get( key )
00552 
00553         if not value:
00554             return 0
00555 
00556         for contained in to_test:
00557             if value.lower().find( contained.lower() ) >= 0:
00558                 return 1
00559 
00560         return 0