Back to index

plone3  3.1.7
PluggableAuthService.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: PluggableAuthService
00016 
00017 $Id: PluggableAuthService.py 79562 2007-09-11 10:12:20Z wichert $
00018 """
00019 
00020 import logging
00021 import sys
00022 import re
00023 import types
00024 
00025 from ZPublisher import BeforeTraverse
00026 
00027 from Acquisition import Implicit, aq_parent, aq_base, aq_inner
00028 
00029 from AccessControl import ClassSecurityInfo, ModuleSecurityInfo
00030 from AccessControl.SecurityManagement import newSecurityManager
00031 from AccessControl.SecurityManagement import getSecurityManager
00032 from AccessControl.SecurityManagement import noSecurityManager
00033 from AccessControl.Permissions import manage_users as ManageUsers
00034 from AccessControl.User import nobody
00035 from AccessControl.SpecialUsers import emergency_user
00036 
00037 from App.ImageFile import ImageFile
00038 
00039 from zExceptions import Unauthorized
00040 from Persistence import PersistentMapping
00041 from OFS.Folder import Folder
00042 from OFS.Cache import Cacheable
00043 from Products.StandardCacheManagers.RAMCacheManager import RAMCacheManager
00044 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00045 from ZTUtils import Batch
00046 from App.class_init import default__class_init__ as InitializeClass
00047 
00048 from OFS.interfaces import IObjectManager
00049 from OFS.interfaces import ISimpleItem
00050 from OFS.interfaces import IPropertyManager
00051 
00052 from Products.PluginRegistry.PluginRegistry import PluginRegistry
00053 import Products
00054 
00055 from zope import event
00056 
00057 from interfaces.authservice import IPluggableAuthService
00058 from interfaces.authservice import _noroles
00059 from interfaces.plugins import IExtractionPlugin
00060 from interfaces.plugins import ILoginPasswordHostExtractionPlugin
00061 from interfaces.plugins import IAuthenticationPlugin
00062 from interfaces.plugins import IChallengePlugin
00063 from interfaces.plugins import ICredentialsUpdatePlugin
00064 from interfaces.plugins import ICredentialsResetPlugin
00065 from interfaces.plugins import IUserFactoryPlugin
00066 from interfaces.plugins import IAnonymousUserFactoryPlugin
00067 from interfaces.plugins import IPropertiesPlugin
00068 from interfaces.plugins import IGroupsPlugin
00069 from interfaces.plugins import IRolesPlugin
00070 from interfaces.plugins import IUpdatePlugin
00071 from interfaces.plugins import IValidationPlugin
00072 from interfaces.plugins import IUserEnumerationPlugin
00073 from interfaces.plugins import IUserAdderPlugin
00074 from interfaces.plugins import IGroupEnumerationPlugin
00075 from interfaces.plugins import IRoleEnumerationPlugin
00076 from interfaces.plugins import IRoleAssignerPlugin
00077 from interfaces.plugins import IChallengeProtocolChooser
00078 from interfaces.plugins import IRequestTypeSniffer
00079 
00080 from events import PrincipalCreated
00081 
00082 from permissions import SearchPrincipals
00083 
00084 from PropertiedUser import PropertiedUser
00085 from utils import _wwwdir
00086 from utils import createViewName
00087 from utils import createKeywords
00088 from utils import classImplements
00089 
00090 security = ModuleSecurityInfo(
00091     'Products.PluggableAuthService.PluggableAuthService' )
00092 
00093 logger = logging.getLogger('PluggableAuthService')
00094 
00095 #   Errors which plugins may raise, and which we suppress:
00096 _SWALLOWABLE_PLUGIN_EXCEPTIONS = ( NameError
00097                                  , AttributeError
00098                                  , KeyError
00099                                  , TypeError
00100                                  , ValueError
00101                                  )
00102 
00103 MultiPlugins = []
00104 def registerMultiPlugin(meta_type):
00105     """ Register a 'multi-plugin' in order to expose it to the Add List
00106     """
00107     if meta_type in MultiPlugins:
00108         raise RuntimeError('Meta-type (%s) already available to Add List'
00109                            % meta_type)
00110     MultiPlugins.append(meta_type)
00111 
00112 class DumbHTTPExtractor( Implicit ):
00113 
00114     security = ClassSecurityInfo()
00115 
00116     security.declarePrivate( 'extractCredentials' )
00117     def extractCredentials( self, request ):
00118 
00119         """ Pull HTTP credentials out of the request.
00120         """
00121         creds = {}
00122         login_pw = request._authUserPW()
00123 
00124         if login_pw is not None:
00125             name, password = login_pw
00126 
00127             creds[ 'login' ] = name
00128             creds[ 'password' ] = password
00129             creds[ 'remote_host' ] = request.get( 'REMOTE_HOST', '' )
00130 
00131             try:
00132                 creds[ 'remote_address' ] = request.getClientAddr()
00133             except AttributeError:
00134                 creds[ 'remote_address' ] = request.get( 'REMOTE_ADDR', '' )
00135 
00136         return creds
00137 
00138 classImplements( DumbHTTPExtractor
00139                , ILoginPasswordHostExtractionPlugin
00140                )
00141 
00142 InitializeClass( DumbHTTPExtractor )
00143 
00144 
00145 class EmergencyUserAuthenticator( Implicit ):
00146 
00147     security = ClassSecurityInfo()
00148 
00149     security.declarePrivate( 'authenticateCredentials' )
00150     def authenticateCredentials( self, credentials ):
00151 
00152         """ Check credentials against the emergency user.
00153         """
00154         if isinstance( credentials, dict ):
00155 
00156             eu = emergency_user
00157             eu_name = eu.getUserName()
00158             login = credentials.get( 'login' )
00159 
00160             if login == eu_name:
00161                 password = credentials.get( 'password' )
00162 
00163                 if eu.authenticate( password, {} ):
00164                     return (eu_name, None)
00165 
00166         return (None, None)
00167 
00168 classImplements( EmergencyUserAuthenticator
00169                , IAuthenticationPlugin
00170                )
00171 
00172 InitializeClass( EmergencyUserAuthenticator )
00173 
00174 
00175 class PluggableAuthService( Folder, Cacheable ):
00176 
00177     """ All-singing, all-dancing user folder.
00178     """
00179     security = ClassSecurityInfo()
00180 
00181     meta_type = 'Pluggable Auth Service'
00182 
00183     _id = id = 'acl_users'
00184 
00185     _emergency_user = emergency_user
00186     _nobody = nobody
00187 
00188     maxlistusers = -1   # Don't allow local role form to try to list us!
00189 
00190     def getId( self ):
00191 
00192         return self._id
00193 
00194     #
00195     #   IUserFolder implementation
00196     #
00197     security.declareProtected( ManageUsers, 'getUser' )
00198     def getUser( self, name ):
00199 
00200         """ See IUserFolder.
00201         """
00202         plugins = self._getOb( 'plugins' )
00203 
00204         user_info = self._verifyUser( plugins, login=name )
00205 
00206         if not user_info:
00207             return None
00208 
00209         return self._findUser( plugins, user_info['id'], user_info['login'])
00210 
00211     security.declareProtected( ManageUsers, 'getUserById' )
00212     def getUserById( self, id, default=None ):
00213 
00214         """ See IUserFolder.
00215         """
00216         plugins = self._getOb( 'plugins' )
00217 
00218         user_info = self._verifyUser( plugins, user_id=id )
00219 
00220         if not user_info:
00221             return default
00222 
00223         return self._findUser( plugins, user_info['id'], user_info['login'])
00224 
00225     security.declarePublic( 'validate' )     # XXX: public?
00226     def validate( self, request, auth='', roles=_noroles ):
00227 
00228         """ See IUserFolder.
00229         """
00230         plugins = self._getOb( 'plugins' )
00231         is_top = self._isTop()
00232 
00233         user_ids = self._extractUserIds(request, plugins)
00234         ( accessed
00235         , container
00236         , name
00237         , value
00238         ) = self._getObjectContext( request[ 'PUBLISHED' ], request )
00239 
00240         for user_id, login in user_ids:
00241 
00242             user = self._findUser(plugins, user_id, login, request=request)
00243 
00244             if aq_base( user ) is emergency_user:
00245 
00246                 if is_top:
00247                     return user
00248                 else:
00249                     return None
00250 
00251             if self._authorizeUser( user
00252                                   , accessed
00253                                   , container
00254                                   , name
00255                                   , value
00256                                   , roles
00257                                   ):
00258                 return user
00259 
00260         if not is_top:
00261             return None
00262 
00263         #
00264         #   No other user folder above us can satisfy, and we have no user;
00265         #   return a constructed anonymous only if anonymous is authorized.
00266         #
00267         anonymous = self._createAnonymousUser( plugins )
00268         if self._authorizeUser( anonymous
00269                               , accessed
00270                               , container
00271                               , name
00272                               , value
00273                               , roles
00274                               ):
00275             return anonymous
00276 
00277         return None
00278 
00279     security.declareProtected( SearchPrincipals, 'searchUsers')
00280     def searchUsers(self, **kw):
00281         """ Search for users
00282         """
00283         search_id = kw.get( 'id', None )
00284         search_name = kw.get( 'name', None )
00285 
00286         result = []
00287         max_results = kw.get('max_results', '')
00288         sort_by = kw.get('sort_by', '')
00289 
00290         # We apply sorting and slicing here across all sets, so don't
00291         # make the plugin do it
00292         if sort_by:
00293             del kw['sort_by']
00294         if max_results:
00295             del kw['max_results']
00296         if search_name:
00297             if kw.get('id') is not None:
00298                 del kw['id'] # don't even bother searching by id
00299             kw['login'] = kw['name']
00300 
00301         plugins = self._getOb( 'plugins' )
00302         enumerators = plugins.listPlugins( IUserEnumerationPlugin )
00303 
00304         for enumerator_id, enum in enumerators:
00305             try:
00306                 user_list = enum.enumerateUsers(**kw)
00307                 for user_info in user_list:
00308                     info = {}
00309                     info.update( user_info )
00310                     info[ 'userid' ] = info[ 'id' ]
00311                     info[ 'principal_type' ] = 'user'
00312                     if not info.has_key('title'):
00313                         info[ 'title' ] = info[ 'login' ]
00314                     result.append(info)
00315 
00316             except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00317                 logger.debug( 'UserEnumerationPlugin %s error' % enumerator_id
00318                             , exc_info=True
00319                             )
00320 
00321         if sort_by:
00322             result.sort( lambda a, b: cmp( a.get(sort_by, '').lower()
00323                                          , b.get(sort_by, '').lower()
00324                                          ) )
00325 
00326         if max_results:
00327             try:
00328                 max_results = int(max_results)
00329                 result = result[:max_results]
00330             except ValueError:
00331                 pass
00332 
00333         return tuple(result)
00334 
00335     security.declareProtected( SearchPrincipals, 'searchGroups')
00336     def searchGroups(self, **kw):
00337         """ Search for groups
00338         """
00339         search_id = kw.get( 'id', None )
00340         search_name = kw.get( 'name', None )
00341 
00342         result = []
00343         max_results = kw.get('max_results', '')
00344         sort_by = kw.get('sort_by', '')
00345 
00346         # We apply sorting and slicing here across all sets, so don't
00347         # make the plugin do it
00348         if sort_by:
00349             del kw['sort_by']
00350         if max_results:
00351             del kw['max_results']
00352         if search_name:
00353             if kw.get('id') is not None:
00354                 del kw['id']
00355             if not kw.has_key('title'):
00356                 kw['title'] = kw['name']
00357 
00358         plugins = self._getOb( 'plugins' )
00359         enumerators = plugins.listPlugins( IGroupEnumerationPlugin )
00360 
00361         for enumerator_id, enum in enumerators:
00362             try:
00363                  group_list = enum.enumerateGroups(**kw)
00364                  for group_info in group_list:
00365                     info = {}
00366                     info.update( group_info )
00367                     info[ 'groupid' ] = info[ 'id' ]
00368                     info[ 'principal_type' ] = 'group'
00369                     if not info.has_key('title'):
00370                         info[ 'title' ] = "(Group) %s" % info[ 'groupid' ]
00371                     result.append(info)
00372             except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00373                 logger.debug( 'GroupEnumerationPlugin %s error' % enumerator_id
00374                             , exc_info=True
00375                             )
00376 
00377         if sort_by:
00378             result.sort( lambda a, b: cmp( a.get(sort_by, '').lower()
00379                                          , b.get(sort_by, '').lower()
00380                                          ) )
00381 
00382         if max_results:
00383             try:
00384                 max_results = int(max_results)
00385                 result = result[:max_results + 1]
00386             except ValueError:
00387                 pass
00388 
00389         return tuple(result)
00390 
00391     security.declareProtected( SearchPrincipals, 'searchPrincipals')
00392     def searchPrincipals(self, groups_first=False, **kw):
00393         """ Search for principals (users, groups, or both)
00394         """
00395         max_results = kw.get( 'max_results', '' )
00396 
00397         search_id = kw.get( 'id', None )
00398         search_name = kw.get( 'name', None )
00399         if search_name:
00400             if not kw.has_key('title'):
00401                 kw['title'] = search_name
00402             kw['login'] = search_name
00403 
00404         users = [ d.copy() for d in self.searchUsers( **kw ) ]
00405         groups = [ d.copy() for d in self.searchGroups( **kw ) ]
00406 
00407         if groups_first:
00408             result = groups + users
00409         else:
00410             result = users + groups
00411 
00412         if max_results:
00413             try:
00414                 max_results = int( max_results )
00415                 result = result[ :max_results + 1 ]
00416             except ValueError:
00417                 pass
00418 
00419         return tuple( result )
00420 
00421     security.declarePrivate( '__creatable_by_emergency_user__' )
00422     def __creatable_by_emergency_user__( self ):
00423         return 1
00424 
00425     security.declarePrivate( '_setObject' )
00426     def _setObject( self, id, object, roles=None, user=None, set_owner=0 ):
00427         #
00428         #   Override ObjectManager's version to change the default for
00429         #   'set_owner' (we don't want to enforce ownership on contained
00430         #   objects).
00431         Folder._setObject( self, id, object, roles, user, set_owner )
00432 
00433     security.declarePrivate( '_delOb' )
00434     def _delOb( self, id ):
00435         #
00436         #   Override ObjectManager's version to clean up any plugin
00437         #   registrations for the deleted object
00438         #
00439         plugins = self._getOb( 'plugins', None )
00440 
00441         if plugins is not None:
00442             plugins.removePluginById( id )
00443 
00444         Folder._delOb( self, id )
00445 
00446     #
00447     # ZMI stuff
00448     #
00449     
00450     arrow_right_gif = ImageFile( 'www/arrow-right.gif', globals() )
00451     arrow_left_gif = ImageFile( 'www/arrow-left.gif', globals() )
00452     arrow_up_gif = ImageFile( 'www/arrow-up.gif', globals() )
00453     arrow_down_gif = ImageFile( 'www/arrow-down.gif', globals() )
00454 
00455     security.declareProtected(ManageUsers, 'manage_search')
00456     manage_search = PageTemplateFile('www/pasSearch', globals())
00457 
00458     manage_options = ( Folder.manage_options[:1]
00459                       + ( { 'label' : 'Search'
00460                           , 'action': 'manage_search' }
00461                         ,
00462                         )
00463                       + Folder.manage_options[2:]
00464                       + Cacheable.manage_options
00465                       )
00466 
00467     security.declareProtected(ManageUsers, 'resultsBatch')
00468     def resultsBatch(self, results, REQUEST, size=20, orphan=2, overlap=0):
00469         """ ZMI helper for getting batching for displaying search results
00470         """
00471         try:
00472             start_val = REQUEST.get('batch_start', '0')
00473             start = int(start_val)
00474             size = int(REQUEST.get('batch_size', size))
00475         except ValueError:
00476             start = 0
00477 
00478         batch = Batch(results, size, start, 0, orphan, overlap)
00479 
00480         if batch.end < len(results):
00481             qs = self._getBatchLink( REQUEST.get('QUERY_STRING', '')
00482                                    , start
00483                                    , batch.end
00484                                    )
00485             REQUEST.set( 'next_batch_url'
00486                        , '%s?%s' % (REQUEST.get('URL'), qs)
00487                        )
00488 
00489         if start > 0:
00490             new_start = start - size - 1
00491 
00492             if new_start < 0:
00493                 new_start = 0
00494 
00495             qs = self._getBatchLink( REQUEST.get('QUERY_STRING', '')
00496                                    , start
00497                                    , new_start
00498                                    )
00499             REQUEST.set( 'previous_batch_url'
00500                        , '%s?%s' % (REQUEST.get('URL'), qs)
00501                        )
00502 
00503         return batch
00504 
00505 
00506     security.declarePrivate('_getBatchLink')
00507     def _getBatchLink(self, qs, old_start, new_start):
00508         """ Internal helper to generate correct query strings
00509         """
00510         if new_start is not None:
00511             if not qs:
00512                 qs = 'batch_start=%d' % new_start
00513             elif qs.startswith('batch_start='):
00514                 qs = qs.replace( 'batch_start=%d' % old_start
00515                                , 'batch_start=%d' % new_start
00516                                )
00517             elif qs.find('&batch_start=') != -1:
00518                 qs = qs.replace( '&batch_start=%d' % old_start
00519                                , '&batch_start=%d' % new_start
00520                                )
00521             else:
00522                 qs = '%s&batch_start=%d' % (qs, new_start)
00523 
00524         return qs
00525 
00526 
00527     #
00528     #   Helper methods
00529     #
00530     security.declarePrivate( '_extractUserIds' )
00531     def _extractUserIds( self, request, plugins ):
00532 
00533         """ request -> [ validated_user_id ]
00534 
00535         o For each set of extracted credentials, try to authenticate
00536           a user;  accumulate a list of the IDs of such users over all
00537           our authentication and extraction plugins.
00538         """
00539         try:
00540             extractors = plugins.listPlugins( IExtractionPlugin )
00541         except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00542             logger.debug('Extractor plugin listing error', exc_info=True)
00543             extractors = ()
00544 
00545         if not extractors:
00546             extractors = ( ( 'default', DumbHTTPExtractor() ), )
00547 
00548         try:
00549             authenticators = plugins.listPlugins( IAuthenticationPlugin )
00550         except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00551             logger.debug('Authenticator plugin listing error', exc_info=True)
00552             authenticators = ()
00553 
00554         result = []
00555 
00556         for extractor_id, extractor in extractors:
00557 
00558             try:
00559                 credentials = extractor.extractCredentials( request )
00560             except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00561                 logger.debug( 'ExtractionPlugin %s error' % extractor_id
00562                             , exc_info=True
00563                             )
00564                 continue
00565 
00566             if credentials:
00567 
00568                 try:
00569                     credentials[ 'extractor' ] = extractor_id # XXX: in key?
00570                     # Test if ObjectCacheEntries.aggregateIndex would work
00571                     items = credentials.items()
00572                     items.sort()
00573                 except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00574                     logger.debug( 'Credentials error: %s' % credentials
00575                                 , exc_info=True
00576                                 )
00577                     continue
00578 
00579                 # First try to authenticate against the emergency
00580                 # user and return immediately if authenticated
00581                 user_id, name = self._tryEmergencyUserAuthentication(
00582                                                             credentials )
00583 
00584                 if user_id is not None:
00585                     return [ ( user_id, name ) ]
00586 
00587                 # Now see if the user ids can be retrieved from the cache
00588                 view_name = createViewName('_extractUserIds', credentials.get('login'))
00589                 keywords = createKeywords(**credentials)
00590                 user_ids = self.ZCacheable_get( view_name=view_name
00591                                               , keywords=keywords
00592                                               , default=None
00593                                               )
00594                 if user_ids is None:
00595                     user_ids = []
00596 
00597                     for authenticator_id, auth in authenticators:
00598 
00599                         try:
00600                             uid_and_info = auth.authenticateCredentials(
00601                                 credentials )
00602 
00603                             if uid_and_info is None:
00604                                 continue
00605 
00606                             user_id, info = uid_and_info
00607 
00608                         except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00609                             msg = 'AuthenticationPlugin %s error' % ( 
00610                                     authenticator_id, )
00611                             logger.debug(msg, exc_info=True) 
00612                             continue
00613 
00614                         if user_id is not None:
00615                             user_ids.append( (user_id, info) )
00616 
00617                     if user_ids:
00618                         self.ZCacheable_set( user_ids
00619                                            , view_name=view_name
00620                                            , keywords=keywords
00621                                            )
00622 
00623                 result.extend( user_ids )
00624 
00625         # Emergency user via HTTP basic auth always wins
00626         user_id, name = self._tryEmergencyUserAuthentication(
00627                 DumbHTTPExtractor().extractCredentials( request ) )
00628 
00629         if user_id is not None:
00630             return [ ( user_id, name ) ]
00631 
00632         return result
00633 
00634     security.declarePrivate( '_tryEmergencyUserAuthentication' )
00635     def _tryEmergencyUserAuthentication( self, credentials ):
00636 
00637         """ credentials -> emergency_user or None
00638         """
00639         try:
00640             eua = EmergencyUserAuthenticator()
00641             user_id, name = eua.authenticateCredentials( credentials )
00642         except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00643             logger.debug('Credentials error: %s' % credentials, exc_info=True)
00644             user_id, name = ( None, None )
00645 
00646         return ( user_id, name )
00647 
00648     security.declarePrivate( '_getGroupsForPrincipal' )
00649     def _getGroupsForPrincipal( self
00650                               , principal
00651                               , request=None
00652                               , plugins=None
00653                               , ignore_plugins=None
00654                               ):
00655         all_groups = {}
00656 
00657         if ignore_plugins is None:
00658             ignore_plugins = ()
00659 
00660         if plugins is None:
00661             plugins = self._getOb( 'plugins' )
00662         groupmakers = plugins.listPlugins( IGroupsPlugin )
00663 
00664         for groupmaker_id, groupmaker in groupmakers:
00665 
00666             if groupmaker_id in ignore_plugins:
00667                 continue
00668             groups = groupmaker.getGroupsForPrincipal( principal, request )
00669             for group in groups:
00670                 principal._addGroups( [ group ] )
00671                 all_groups[ group ] = 1
00672 
00673         return all_groups.keys()
00674 
00675     security.declarePrivate( '_createAnonymousUser' )
00676     def _createAnonymousUser( self, plugins ):
00677 
00678         """ Allow IAnonymousUserFactoryPlugins to create or fall back.
00679         """
00680         factories = plugins.listPlugins( IAnonymousUserFactoryPlugin )
00681 
00682         for factory_id, factory in factories:
00683 
00684             anon = factory.createAnonymousUser()
00685 
00686             if anon is not None:
00687                 return anon.__of__( self )
00688 
00689         return nobody.__of__( self )
00690 
00691     security.declarePrivate( '_createUser' )
00692     def _createUser( self, plugins, user_id, name ):
00693 
00694         """ Allow IUserFactoryPlugins to create, or fall back to default.
00695         """
00696         factories = plugins.listPlugins( IUserFactoryPlugin )
00697 
00698         for factory_id, factory in factories:
00699 
00700             user = factory.createUser( user_id, name )
00701 
00702             if user is not None:
00703                 return user.__of__( self )
00704 
00705         return PropertiedUser( user_id, name ).__of__( self )
00706 
00707     security.declarePrivate( '_findUser' )
00708     def _findUser( self, plugins, user_id, name=None, request=None ):
00709 
00710         """ user_id -> decorated_user
00711         """
00712         if user_id == self._emergency_user.getUserName():
00713             return self._emergency_user
00714 
00715         # See if the user can be retrieved from the cache
00716         view_name = createViewName('_findUser', user_id)
00717         keywords = createKeywords(user_id=user_id, name=name)
00718         user = self.ZCacheable_get( view_name=view_name
00719                                   , keywords=keywords
00720                                   , default=None
00721                                   )
00722 
00723         if user is None:
00724 
00725             user = self._createUser( plugins, user_id, name )
00726             propfinders = plugins.listPlugins( IPropertiesPlugin )
00727 
00728             for propfinder_id, propfinder in propfinders:
00729 
00730                 data = propfinder.getPropertiesForUser( user, request )
00731                 if data:
00732                     user.addPropertysheet( propfinder_id, data )
00733 
00734             groups = self._getGroupsForPrincipal( user, request
00735                                                 , plugins=plugins )
00736             user._addGroups( groups )
00737 
00738             rolemakers = plugins.listPlugins( IRolesPlugin )
00739 
00740             for rolemaker_id, rolemaker in rolemakers:
00741 
00742                 roles = rolemaker.getRolesForPrincipal( user, request )
00743 
00744                 if roles:
00745                     user._addRoles( roles )
00746 
00747             user._addRoles( ['Authenticated'] )
00748 
00749             # Cache the user if caching is enabled
00750             base_user = aq_base(user)
00751             if getattr(base_user, '_p_jar', None) is None:
00752                 self.ZCacheable_set( base_user
00753                                    , view_name=view_name
00754                                    , keywords=keywords
00755                                    )
00756 
00757         return user.__of__( self )
00758 
00759     security.declarePrivate( '_verifyUser' )
00760     def _verifyUser( self, plugins, user_id=None, login=None ):
00761 
00762         """ user_id -> info_dict or None
00763         """
00764         criteria = {'exact_match': True}
00765 
00766         if user_id is not None:
00767             criteria[ 'id' ] = user_id
00768 
00769         if login is not None:
00770             criteria[ 'login' ] = login
00771 
00772         if criteria:
00773             view_name = createViewName('_verifyUser', user_id or login)
00774             keywords = createKeywords(**criteria)
00775             cached_info = self.ZCacheable_get( view_name=view_name
00776                                              , keywords=keywords
00777                                              , default=None
00778                                              )
00779 
00780             if cached_info is not None:
00781                 return cached_info
00782 
00783 
00784             enumerators = plugins.listPlugins( IUserEnumerationPlugin )
00785 
00786             for enumerator_id, enumerator in enumerators:
00787                 try:
00788                     info = enumerator.enumerateUsers( **criteria )
00789 
00790                     if info:
00791                         # Put the computed value into the cache
00792                         self.ZCacheable_set( info[0]
00793                                            , view_name=view_name
00794                                            , keywords=keywords
00795                                            )
00796                         return info[0]
00797 
00798                 except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00799                     msg = 'UserEnumerationPlugin %s error' % enumerator_id
00800                     logger.debug(msg, exc_info=True)
00801 
00802         return None
00803 
00804     security.declarePrivate( '_authorizeUser' )
00805     def _authorizeUser( self
00806                       , user
00807                       , accessed
00808                       , container
00809                       , name
00810                       , value
00811                       , roles=_noroles
00812                       ):
00813 
00814         """ -> boolean (whether user has roles).
00815 
00816         o Add the user to the SM's stack, if successful.
00817 
00818         o Return
00819         """
00820         user = aq_base( user ).__of__( self )
00821         newSecurityManager( None, user )
00822         security = getSecurityManager()
00823         try:
00824             try:
00825                 if roles is _noroles:
00826                     if security.validate( accessed
00827                                         , container
00828                                         , name
00829                                         , value
00830                                         ):
00831                         return 1
00832                 else:
00833                     if security.validate( accessed
00834                                         , container
00835                                         , name
00836                                         , value
00837                                         , roles
00838                                         ):
00839                         return 1
00840             except:
00841                 noSecurityManager()
00842                 raise
00843 
00844         except Unauthorized:
00845             pass
00846 
00847         return 0
00848 
00849 
00850     security.declarePrivate( '_isTop' )
00851     def _isTop( self ):
00852 
00853         """ Are we the user folder in the root object?
00854         """
00855         try:
00856             parent = aq_base( aq_parent( self ) )
00857             if parent is None:
00858                 return 0
00859             return parent.isTopLevelPrincipiaApplicationObject
00860         except AttributeError:
00861             return 0
00862 
00863 
00864     security.declarePrivate( '_getObjectContext' )
00865     def _getObjectContext( self, v, request ):
00866 
00867         """ request -> ( a, c, n, v )
00868 
00869         o 'a 'is the object the object was accessed through
00870 
00871         o 'c 'is the physical container of the object
00872 
00873         o 'n 'is the name used to access the object
00874 
00875         o 'v' is the object (value) we're validating access to
00876 
00877         o XXX:  Lifted from AccessControl.User.BasicUserFolder._getobcontext
00878         """
00879         if len( request.steps ) == 0: # someone deleted root index_html
00880 
00881             request[ 'RESPONSE' ].notFoundError(
00882                 'no default view (root default view was probably deleted)' )
00883 
00884         root = request[ 'PARENTS' ][ -1 ]
00885         request_container = aq_parent( root )
00886 
00887         n = request.steps[ -1 ]
00888 
00889         # default to accessed and container as v.aq_parent
00890         a = c = request[ 'PARENTS' ][ 0 ]
00891 
00892         # try to find actual container
00893         inner = aq_inner( v )
00894         innerparent = aq_parent( inner )
00895 
00896         if innerparent is not None:
00897 
00898             # this is not a method, we needn't treat it specially
00899             c = innerparent
00900 
00901         elif hasattr(v, 'im_self'):
00902 
00903             # this is a method, we need to treat it specially
00904             c = v.im_self
00905             c = aq_inner( v )
00906 
00907         # if pub's aq_parent or container is the request container, it
00908         # means pub was accessed from the root
00909         if a is request_container:
00910             a = root
00911 
00912         if c is request_container:
00913             c = root
00914 
00915         return a, c, n, v
00916 
00917     security.declarePrivate( '_getEmergencyUser' )
00918     def _getEmergencyUser( self ):
00919 
00920         return emergency_user.__of__( self )
00921 
00922 
00923     security.declarePrivate( '_doAddUser' )
00924     def _doAddUser( self, login, password, roles, domains, **kw ):
00925         """ Create a user with login, password and roles if, and only if,
00926             we have a registered user manager and role manager that will
00927             accept specific plugin interfaces.
00928         """
00929         plugins = self._getOb( 'plugins' )
00930         useradders = plugins.listPlugins( IUserAdderPlugin )
00931         roleassigners = plugins.listPlugins( IRoleAssignerPlugin )
00932 
00933         user = None
00934 
00935         if not (useradders and roleassigners):
00936             raise NotImplementedError( "There are no plugins"
00937                                        " that can create"
00938                                        " users and assign roles to them." )
00939 
00940         for useradder_id, useradder in useradders:
00941             if useradder.doAddUser( login, password ):
00942                 # XXX: Adds user to cache, but without roles...
00943                 user = self.getUser( login )
00944                 break
00945 
00946         # XXX What should we do if no useradder was succesfull?
00947 
00948         for roleassigner_id, roleassigner in roleassigners:
00949             for role in roles:
00950                 try:
00951                     roleassigner.doAssignRoleToPrincipal( user.getId(), role )
00952                 except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
00953                     logger.debug( 'RoleAssigner %s error' % roleassigner_id
00954                                 , exc_info=True
00955                                 )
00956                     pass
00957 
00958         if user is not None:
00959             event.notify(PrincipalCreated(user))
00960 
00961 
00962     security.declarePublic('all_meta_types')
00963     def all_meta_types(self):
00964         """ What objects can be put in here?
00965         """
00966         allowed_types = tuple(MultiPlugins) + (RAMCacheManager.meta_type,)
00967 
00968         return [x for x in Products.meta_types if x['name'] in allowed_types]
00969 
00970     security.declarePrivate( 'manage_beforeDelete' )
00971     def manage_beforeDelete(self, item, container):
00972         if item is self:
00973             try:
00974                 del container.__allow_groups__
00975             except:
00976                 pass
00977 
00978             handle = self.meta_type + '/' + self.getId()
00979             BeforeTraverse.unregisterBeforeTraverse(container, handle)
00980 
00981     security.declarePrivate( 'manage_afterAdd' )
00982     def manage_afterAdd(self, item, container):
00983         if item is self:
00984             container.__allow_groups__ = aq_base(self)
00985 
00986             handle = self.meta_type + '/' + self.getId()
00987             container = container.this()
00988             nc = BeforeTraverse.NameCaller(self.getId())
00989             BeforeTraverse.registerBeforeTraverse(container, nc, handle)
00990 
00991     def __call__(self, container, req):
00992         """ The __before_publishing_traverse__ hook.
00993         """
00994         resp = req['RESPONSE']
00995         req._hold(ResponseCleanup(resp))
00996         stack = getattr(resp, '_unauthorized_stack', [])
00997         stack.append(resp._unauthorized)
00998         resp._unauthorized_stack = stack
00999         resp._unauthorized = self._unauthorized
01000         resp._has_challenged = False
01001 
01002     #
01003     # Response override
01004     #
01005     def _unauthorized(self):
01006         req = self.REQUEST
01007         resp = req['RESPONSE']
01008         if resp._has_challenged: # Been here already
01009             return
01010         if not self.challenge(req, resp):
01011             # Need to fall back here
01012             resp = self._cleanupResponse()
01013             resp._unauthorized()
01014         else:
01015             resp._has_challenged = True
01016 
01017     def challenge(self, request, response):
01018         plugins = self._getOb('plugins')
01019 
01020         # Find valid protocols for this request type
01021         valid_protocols = []
01022         choosers = []
01023         try:
01024             choosers = plugins.listPlugins( IChallengeProtocolChooser )
01025         except KeyError:
01026             # Work around the fact that old instances might not have
01027             # IChallengeProtocolChooser registered with the
01028             # PluginRegistry.
01029             pass
01030 
01031         for chooser_id, chooser in choosers:
01032             choosen = chooser.chooseProtocols(request)
01033             if choosen is None:
01034                 continue
01035             valid_protocols.extend(choosen)
01036 
01037         # Go through all challenge plugins
01038         challengers = plugins.listPlugins( IChallengePlugin )
01039 
01040         protocol = None
01041 
01042         for challenger_id, challenger in challengers:
01043             challenger_protocol = getattr(challenger, 'protocol',
01044                                           challenger_id)
01045             if valid_protocols and challenger_protocol not in valid_protocols:
01046                 # Skip invalid protocol for this request type.
01047                 continue
01048             if protocol is None or protocol == challenger_protocol:
01049                 if challenger.challenge(request, response):
01050                     protocol = challenger_protocol
01051 
01052         if protocol is not None:
01053             # something fired, so it was a successful PAS challenge
01054             return True
01055 
01056         # nothing fired, so trigger the fallback
01057         return False
01058 
01059     def _cleanupResponse(self):
01060         resp = self.REQUEST['RESPONSE']
01061         # No errors of any sort may propagate, and we don't care *what*
01062         # they are, even to log them.
01063         stack = getattr(resp, '_unauthorized_stack', [])
01064 
01065         if stack:
01066             resp._unauthorized = stack.pop()
01067         else:
01068             try:
01069                 del resp._unauthorized
01070             except:
01071                 pass
01072 
01073         return resp
01074 
01075     security.declarePublic( 'hasUsers' )
01076     def hasUsers(self):
01077         """Zope quick start sacrifice.
01078 
01079         The quick start page expects a hasUsers() method.
01080         """
01081         return True
01082 
01083     security.declarePublic('updateCredentials')
01084     def updateCredentials(self, request, response, login, new_password):
01085         """Central updateCredentials method
01086 
01087         This method is needed for cases where the credentials storage and
01088         the credentials extraction is handled by different plugins. Example
01089         case would be if the CookieAuthHelper is used as a Challenge and
01090         Extraction plugin only to take advantage of the login page feature
01091         but the credentials are not stored in the CookieAuthHelper cookie
01092         but somewhere else, like in a Session.
01093         """
01094         plugins = self._getOb('plugins')
01095         cred_updaters = plugins.listPlugins(ICredentialsUpdatePlugin)
01096 
01097         for updater_id, updater in cred_updaters:
01098             updater.updateCredentials(request, response, login, new_password)
01099 
01100 
01101     security.declarePublic('logout')
01102     def logout(self, REQUEST):
01103         """Publicly accessible method to log out a user
01104         """
01105         self.resetCredentials(REQUEST, REQUEST['RESPONSE'])
01106 
01107         # Little bit of a hack: Issuing a redirect to the same place
01108         # where the user was so that in the second request the now-destroyed
01109         # credentials can be acted upon to e.g. go back to the login page
01110         referrer = REQUEST.get('HTTP_REFERER') # HTTP_REFERER is optional header
01111         if referrer:
01112             REQUEST['RESPONSE'].redirect(referrer)
01113 
01114     security.declarePublic('resetCredentials')
01115     def resetCredentials(self, request, response):
01116         """Reset credentials by informing all active resetCredentials plugins
01117         """
01118         user = getSecurityManager().getUser()
01119         if aq_base(user) is not nobody:
01120             plugins = self._getOb('plugins')
01121             cred_resetters = plugins.listPlugins(ICredentialsResetPlugin)
01122 
01123             for resetter_id, resetter in cred_resetters:
01124                 resetter.resetCredentials(request, response)
01125 
01126 classImplements( PluggableAuthService
01127                , (IPluggableAuthService, IObjectManager, IPropertyManager)
01128                )
01129 
01130 InitializeClass( PluggableAuthService )
01131 
01132 class ResponseCleanup:
01133     def __init__(self, resp):
01134         self.resp = resp
01135 
01136     def __del__(self):
01137         # Free the references.
01138         #
01139         # No errors of any sort may propagate, and we don't care *what*
01140         # they are, even to log them.
01141         stack = getattr(self.resp, '_unauthorized_stack', [])
01142         old = None
01143 
01144         while stack:
01145             old = stack.pop()
01146 
01147         if old is not None:
01148             self.resp._unauthorized = old
01149         else:
01150             try:
01151                 del self.resp._unauthorized
01152             except:
01153                 pass
01154 
01155         try:
01156             del self.resp
01157         except:
01158             pass
01159 
01160 _PLUGIN_TYPE_INFO = (
01161     ( IExtractionPlugin
01162     , 'IExtractionPlugin'
01163     , 'extraction'
01164     , "Extraction plugins are responsible for extracting credentials "
01165       "from the request."
01166     )
01167   , ( IAuthenticationPlugin
01168     , 'IAuthenticationPlugin'
01169     , 'authentication'
01170     , "Authentication plugins are responsible for validating credentials "
01171       "generated by the Extraction Plugin."
01172     )
01173   , ( IChallengePlugin
01174     , 'IChallengePlugin'
01175     , 'challenge'
01176     , "Challenge plugins initiate a challenge to the user to provide "
01177       "credentials."
01178     )
01179   , ( ICredentialsUpdatePlugin
01180     , 'ICredentialsUpdatePlugin'
01181     , 'update credentials'
01182     , "Credential update plugins respond to the user changing "
01183       "credentials."
01184     )
01185   , ( ICredentialsResetPlugin
01186     , 'ICredentialsResetPlugin'
01187     , 'reset credentials'
01188     , "Credential clear plugins respond to a user logging out."
01189     )
01190   , ( IUserFactoryPlugin
01191     , 'IUserFactoryPlugin'
01192     , 'userfactory'
01193     , "Create users."
01194     )
01195   , ( IAnonymousUserFactoryPlugin
01196     , 'IAnonymousUserFactoryPlugin'
01197     , 'anonymoususerfactory'
01198     , "Create anonymous users."
01199     )
01200   , ( IPropertiesPlugin
01201     , 'IPropertiesPlugin'
01202     , 'properties'
01203     , "Properties plugins generate property sheets for users."
01204     )
01205   , ( IGroupsPlugin
01206     , 'IGroupsPlugin'
01207     , 'groups'
01208     , "Groups plugins determine the groups to which a user belongs."
01209     )
01210   , ( IRolesPlugin
01211     , 'IRolesPlugin'
01212     , 'roles'
01213     , "Roles plugins determine the global roles which a user has."
01214     )
01215   , ( IUpdatePlugin
01216     , 'IUpdatePlugin'
01217     , 'update'
01218     , "Update plugins allow the user or the application to update "
01219       "the user's properties."
01220     )
01221   , ( IValidationPlugin
01222     , 'IValidationPlugin'
01223     , 'validation'
01224     , "Validation plugins specify allowable values for user properties "
01225       "(e.g., minimum password length, allowed characters, etc.)"
01226     )
01227   , ( IUserEnumerationPlugin
01228     , 'IUserEnumerationPlugin'
01229     , 'user_enumeration'
01230     , "Enumeration plugins allow querying users by ID, and searching for "
01231       "users who match particular criteria."
01232     )
01233   , ( IUserAdderPlugin
01234     , 'IUserAdderPlugin'
01235     , 'user_adder'
01236     , "User Adder plugins allow the Pluggable Auth Service to create users."
01237     )
01238   , ( IGroupEnumerationPlugin
01239     , 'IGroupEnumerationPlugin'
01240     , 'group_enumeration'
01241     , "Enumeration plugins allow querying groups by ID."
01242     )
01243   , ( IRoleEnumerationPlugin
01244     , 'IRoleEnumerationPlugin'
01245     , 'role_enumeration'
01246     , "Enumeration plugins allow querying roles by ID."
01247     )
01248   , ( IRoleAssignerPlugin
01249     , 'IRoleAssignerPlugin'
01250     , 'role_assigner'
01251     , "Role Assigner plugins allow the Pluggable Auth Service to assign"
01252       " roles to principals."
01253     )
01254   , ( IChallengeProtocolChooser
01255     , 'IChallengeProtocolChooser'
01256     , 'challenge_protocol_chooser'
01257     , "Challenge Protocol Chooser plugins decide what authorization"
01258       "protocol to use for a given request type."
01259     )
01260   , ( IRequestTypeSniffer
01261     , 'IRequestTypeSniffer'
01262     , 'request_type_sniffer'
01263     , "Request Type Sniffer plugins detect the type of an incoming request."
01264     )
01265   )
01266 
01267 def addPluggableAuthService( dispatcher
01268                            , base_profile=None
01269                            , extension_profiles=()
01270                            , create_snapshot=True
01271                            , setup_tool_id='setup_tool'
01272                            , REQUEST=None
01273                            ):
01274     """ Add a PluggableAuthService to 'dispatcher'.
01275 
01276     o BBB for non-GenericSetup use.
01277     """
01278     pas = PluggableAuthService()
01279     preg = PluginRegistry( _PLUGIN_TYPE_INFO )
01280     preg._setId( 'plugins' )
01281     pas._setObject( 'plugins', preg )
01282     dispatcher._setObject( pas.getId(), pas )
01283 
01284     if REQUEST is not None:
01285         REQUEST['RESPONSE'].redirect(
01286                                 '%s/manage_workspace'
01287                                 '?manage_tabs_message='
01288                                 'PluggableAuthService+added.'
01289                               % dispatcher.absolute_url() )
01290 
01291 def addConfiguredPASForm(dispatcher):
01292     """ Wrap the PTF in 'dispatcher', including 'profile_registry' in options.
01293     """
01294     from Products.GenericSetup import EXTENSION
01295     from Products.GenericSetup import profile_registry
01296 
01297     wrapped = PageTemplateFile( 'pasAddForm', _wwwdir ).__of__( dispatcher )
01298 
01299     base_profiles = []
01300     extension_profiles = []
01301 
01302     for info in profile_registry.listProfileInfo(for_=IPluggableAuthService):
01303         if info.get('type') == EXTENSION:
01304             extension_profiles.append(info)
01305         else:
01306             base_profiles.append(info)
01307 
01308     return wrapped( base_profiles=tuple(base_profiles),
01309                     extension_profiles =tuple(extension_profiles) )
01310 
01311 def addConfiguredPAS( dispatcher
01312                     , base_profile
01313                     , extension_profiles=()
01314                     , create_snapshot=True
01315                     , setup_tool_id='setup_tool'
01316                     , REQUEST=None
01317                     ):
01318     """ Add a PluggableAuthService to 'self.
01319     """
01320     from Products.GenericSetup.tool import SetupTool
01321 
01322     pas = PluggableAuthService()
01323     preg = PluginRegistry( _PLUGIN_TYPE_INFO )
01324     preg._setId( 'plugins' )
01325     pas._setObject( 'plugins', preg )
01326     dispatcher._setObject( pas.getId(), pas )
01327 
01328     pas = dispatcher._getOb( pas.getId() )    # wrapped
01329     tool = SetupTool( setup_tool_id )
01330     pas._setObject( tool.getId(), tool )
01331 
01332     tool = pas._getOb( tool.getId() )       # wrapped
01333     tool.setImportContext( 'profile-%s' % base_profile )
01334     tool.runAllImportSteps()
01335 
01336     for extension_profile in extension_profiles:
01337         tool.setImportContext( 'profile-%s' % extension_profile )
01338         tool.runAllImportSteps()
01339 
01340     tool.setImportContext( 'profile-%s' % base_profile )
01341 
01342     if create_snapshot:
01343         tool.createSnapshot( 'initial_configuration' )
01344 
01345     if REQUEST is not None:
01346         REQUEST['RESPONSE'].redirect(
01347                                 '%s/manage_workspace'
01348                                 '?manage_tabs_message='
01349                                 'PluggableAuthService+added.'
01350                               % dispatcher.absolute_url() )
01351