Back to index

plone3  3.1.7
MembershipTool.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Basic membership tool.
00014 
00015 $Id: MembershipTool.py 77182 2007-06-28 17:25:27Z yuppie $
00016 """
00017 import logging
00018 from warnings import warn
00019 
00020 from AccessControl import ClassSecurityInfo
00021 from AccessControl.requestmethod import postonly
00022 from AccessControl.User import nobody
00023 from Acquisition import aq_base
00024 from Acquisition import aq_inner
00025 from Acquisition import aq_parent
00026 from Globals import DTMLFile
00027 from Globals import InitializeClass
00028 from Globals import MessageDialog
00029 from Globals import PersistentMapping
00030 from OFS.Folder import Folder
00031 from ZODB.POSException import ConflictError
00032 from zope.component import getUtility
00033 from zope.interface import implements
00034 
00035 from exceptions import AccessControl_Unauthorized
00036 from exceptions import BadRequest
00037 from interfaces import IMembershipTool
00038 from interfaces import ISiteRoot
00039 from interfaces.portal_membership \
00040         import portal_membership as z2IMembershipTool
00041 from permissions import AccessContentsInformation
00042 from permissions import ChangeLocalRoles
00043 from permissions import ListPortalMembers
00044 from permissions import ManagePortal
00045 from permissions import ManageUsers
00046 from permissions import SetOwnPassword
00047 from permissions import View
00048 from utils import _checkPermission
00049 from utils import _dtmldir
00050 from utils import _getAuthenticatedUser
00051 from utils import getToolByName
00052 from utils import UniqueObject
00053 
00054 logger = logging.getLogger('CMFCore.MembershipTool')
00055 
00056 
00057 class MembershipTool(UniqueObject, Folder):
00058 
00059     """ This tool accesses member data through an acl_users object.
00060 
00061     It can be replaced with something that accesses member data in a
00062     different way.
00063     """
00064 
00065     implements(IMembershipTool)
00066     __implements__ = (z2IMembershipTool, )
00067 
00068     id = 'portal_membership'
00069     meta_type = 'CMF Membership Tool'
00070     memberareaCreationFlag = 1
00071 
00072     security = ClassSecurityInfo()
00073 
00074     manage_options=( ({ 'label' : 'Configuration'
00075                      , 'action' : 'manage_mapRoles'
00076                      },) +
00077                    ( { 'label' : 'Overview'
00078                      , 'action' : 'manage_overview'
00079                      },
00080                    ) + Folder.manage_options)
00081 
00082     #
00083     #   ZMI methods
00084     #
00085     security.declareProtected(ManagePortal, 'manage_overview')
00086     manage_overview = DTMLFile( 'explainMembershipTool', _dtmldir )
00087 
00088     #
00089     #   'portal_membership' interface methods
00090     #
00091     security.declareProtected(ManagePortal, 'manage_mapRoles')
00092     manage_mapRoles = DTMLFile('membershipRolemapping', _dtmldir )
00093 
00094     security.declareProtected(SetOwnPassword, 'setPassword')
00095     @postonly
00096     def setPassword(self, password, domains=None, REQUEST=None):
00097         '''Allows the authenticated member to set his/her own password.
00098         '''
00099         # XXX: this method violates the rules for tools/utilities:
00100         # it depends on a non-utility tool
00101         registration = getToolByName(self, 'portal_registration', None)
00102         if not self.isAnonymousUser():
00103             member = self.getAuthenticatedMember()
00104             if registration:
00105                 failMessage = registration.testPasswordValidity(password)
00106                 if failMessage is not None:
00107                     raise BadRequest(failMessage)
00108             member.setSecurityProfile(password=password, domains=domains)
00109         else:
00110             raise BadRequest('Not logged in.')
00111 
00112     security.declarePublic('getAuthenticatedMember')
00113     def getAuthenticatedMember(self):
00114         '''
00115         Returns the currently authenticated member object
00116         or the Anonymous User.  Never returns None.
00117         '''
00118         u = _getAuthenticatedUser(self)
00119         if u is None:
00120             u = nobody
00121         return self.wrapUser(u)
00122 
00123     security.declarePrivate('wrapUser')
00124     def wrapUser(self, u, wrap_anon=0):
00125         """ Set up the correct acquisition wrappers for a user object.
00126 
00127         Provides an opportunity for a portal_memberdata tool to retrieve and
00128         store member data independently of the user object.
00129         """
00130         # XXX: this method violates the rules for tools/utilities:
00131         # it depends on a non-utility tool
00132         b = getattr(u, 'aq_base', None)
00133         if b is None:
00134             # u isn't wrapped at all.  Wrap it in self.acl_users.
00135             b = u
00136             u = u.__of__(self.acl_users)
00137         if (b is nobody and not wrap_anon) or hasattr(b, 'getMemberId'):
00138             # This user is either not recognized by acl_users or it is
00139             # already registered with something that implements the
00140             # member data tool at least partially.
00141             return u
00142 
00143         # Apply any role mapping if we have it
00144         if hasattr(self, 'role_map'):
00145             for portal_role in self.role_map.keys():
00146                 if (self.role_map.get(portal_role) in u.roles and
00147                         portal_role not in u.roles):
00148                     u.roles.append(portal_role)
00149 
00150         mdtool = getToolByName(self, 'portal_memberdata', None)
00151         if mdtool is not None:
00152             try:
00153                 u = mdtool.wrapUser(u)
00154             except ConflictError:
00155                 raise
00156             except:
00157                 logger.exception("Error during wrapUser")
00158         return u
00159 
00160     security.declareProtected(ManagePortal, 'getPortalRoles')
00161     def getPortalRoles(self):
00162         """
00163         Return all local roles defined by the portal itself,
00164         which means roles that are useful and understood
00165         by the portal object
00166         """
00167         parent = self.aq_inner.aq_parent
00168         roles = list( parent.userdefined_roles() )
00169 
00170         # This is *not* a local role in the portal but used by it
00171         roles.append('Manager')
00172         roles.append('Owner')
00173 
00174         return roles
00175 
00176     security.declareProtected(ManagePortal, 'setRoleMapping')
00177     @postonly
00178     def setRoleMapping(self, portal_role, userfolder_role, REQUEST=None):
00179         """
00180         set the mapping of roles between roles understood by
00181         the portal and roles coming from outside user sources
00182         """
00183         if not hasattr(self, 'role_map'): self.role_map = PersistentMapping()
00184 
00185         if len(userfolder_role) < 1:
00186             del self.role_map[portal_role]
00187         else:
00188             self.role_map[portal_role] = userfolder_role
00189 
00190         return MessageDialog(
00191                title  ='Mapping updated',
00192                message='The Role mappings have been updated',
00193                action ='manage_mapRoles')
00194 
00195     security.declareProtected(ManagePortal, 'getMappedRole')
00196     def getMappedRole(self, portal_role):
00197         """
00198         returns a role name if the portal role is mapped to
00199         something else or an empty string if it is not
00200         """
00201         if hasattr(self, 'role_map'):
00202             return self.role_map.get(portal_role, '')
00203         else:
00204             return ''
00205 
00206     security.declarePublic('getMembersFolder')
00207     def getMembersFolder(self):
00208         """ Get the members folder object.
00209         """
00210         parent = aq_parent( aq_inner(self) )
00211         members = getattr(parent, 'Members', None)
00212         return members
00213 
00214     security.declareProtected(ManagePortal, 'getMemberareaCreationFlag')
00215     def getMemberareaCreationFlag(self):
00216         """
00217         Returns the flag indicating whether the membership tool
00218         will create a member area if an authenticated user from
00219         an underlying user folder logs in first without going
00220         through the join process
00221         """
00222         return self.memberareaCreationFlag
00223 
00224     security.declareProtected(ManagePortal, 'setMemberareaCreationFlag')
00225     def setMemberareaCreationFlag(self):
00226         """
00227         sets the flag indicating whether the membership tool
00228         will create a member area if an authenticated user from
00229         an underlying user folder logs in first without going
00230         through the join process
00231         """
00232         if not hasattr(self, 'memberareaCreationFlag'):
00233             self.memberareaCreationFlag = 0
00234 
00235         if self.memberareaCreationFlag == 0:
00236             self.memberareaCreationFlag = 1
00237         else:
00238             self.memberareaCreationFlag = 0
00239 
00240         return MessageDialog(
00241                title  ='Member area creation flag changed',
00242                message='Member area creation flag has been updated',
00243                action ='manage_mapRoles')
00244 
00245     security.declarePublic('createMemberArea')
00246     def createMemberArea(self, member_id=''):
00247         """ Create a member area for 'member_id' or authenticated user.
00248         """
00249         if not self.getMemberareaCreationFlag():
00250             return None
00251         members = self.getMembersFolder()
00252         if members is None:
00253             return None
00254         if self.isAnonymousUser():
00255             return None
00256         # Note: We can't use getAuthenticatedMember() and getMemberById()
00257         # because they might be wrapped by MemberDataTool.
00258         user = _getAuthenticatedUser(self)
00259         user_id = user.getId()
00260         if member_id in ('', user_id):
00261             member = user
00262             member_id = user_id
00263         else:
00264             if _checkPermission(ManageUsers, self):
00265                 uf = self._huntUserFolder(member_id, self)
00266                 if uf:
00267                     member = uf.getUserById(member_id).__of__(uf)
00268                 else:
00269                     raise ValueError('Member %s does not exist' % member_id)
00270             else:
00271                 return None
00272         if hasattr( aq_base(members), member_id ):
00273             return None
00274         else:
00275             f_title = "%s's Home" % member_id
00276             members.manage_addPortalFolder( id=member_id, title=f_title )
00277             f=getattr(members, member_id)
00278 
00279             f.manage_permission(View,
00280                                 ['Owner','Manager','Reviewer'], 0)
00281             f.manage_permission(AccessContentsInformation,
00282                                 ['Owner','Manager','Reviewer'], 0)
00283 
00284             # Grant Ownership and Owner role to Member
00285             f.changeOwnership(member)
00286             f.__ac_local_roles__ = None
00287             f.manage_setLocalRoles(member_id, ['Owner'])
00288         return f
00289 
00290     security.declarePublic('createMemberarea')
00291     createMemberarea = createMemberArea
00292 
00293     security.declareProtected(ManageUsers, 'deleteMemberArea')
00294     @postonly
00295     def deleteMemberArea(self, member_id, REQUEST=None):
00296         """ Delete member area of member specified by member_id.
00297         """
00298         members = self.getMembersFolder()
00299         if not members:
00300             return 0
00301         if hasattr( aq_base(members), member_id ):
00302             members.manage_delObjects(member_id)
00303             return 1
00304         else:
00305             return 0
00306 
00307     security.declarePublic('isAnonymousUser')
00308     def isAnonymousUser(self):
00309         '''
00310         Returns 1 if the user is not logged in.
00311         '''
00312         u = _getAuthenticatedUser(self)
00313         if u is None or u.getUserName() == 'Anonymous User':
00314             return 1
00315         return 0
00316 
00317     security.declarePublic('checkPermission')
00318     def checkPermission(self, permissionName, object, subobjectName=None):
00319         '''
00320         Checks whether the current user has the given permission on
00321         the given object or subobject.
00322         '''
00323         if subobjectName is not None:
00324             object = getattr(object, subobjectName)
00325         return _checkPermission(permissionName, object)
00326 
00327     security.declarePublic('credentialsChanged')
00328     def credentialsChanged(self, password, REQUEST=None):
00329         '''
00330         Notifies the authentication mechanism that this user has changed
00331         passwords.  This can be used to update the authentication cookie.
00332         Note that this call should *not* cause any change at all to user
00333         databases.
00334         '''
00335         # XXX: this method violates the rules for tools/utilities:
00336         # it depends on self.REQUEST
00337         if REQUEST is None:
00338             REQUEST = self.REQUEST
00339             warn("credentialsChanged should be called with 'REQUEST' as "
00340                  "second argument. The BBB code will be removed in CMF 2.3.",
00341                  DeprecationWarning, stacklevel=2)
00342 
00343         if not self.isAnonymousUser():
00344             acl_users = self.acl_users
00345             user = _getAuthenticatedUser(self)
00346             name = user.getUserName()
00347             # this really does need to be the user name, and not the user id,
00348             # because we're dealing with authentication credentials
00349             if hasattr(acl_users.aq_base, 'credentialsChanged'):
00350                 # Use an interface provided by LoginManager.
00351                 acl_users.credentialsChanged(user, name, password)
00352             else:
00353                 p = getattr(REQUEST, '_credentials_changed_path', None)
00354                 if p is not None:
00355                     # Use an interface provided by CookieCrumbler.
00356                     change = self.restrictedTraverse(p)
00357                     change(user, name, password)
00358 
00359     security.declareProtected(ManageUsers, 'getMemberById')
00360     def getMemberById(self, id):
00361         '''
00362         Returns the given member.
00363         '''
00364         user = self._huntUser(id, self)
00365         if user is not None:
00366             user = self.wrapUser(user)
00367         return user
00368 
00369     def _huntUserFolder(self, username, context):
00370         """Find userfolder containing user in the hierarchy
00371            starting from context
00372         """
00373         uf = context.acl_users
00374         while uf is not None:
00375             user = uf.getUserById(username)
00376             if user is not None:
00377                 return uf
00378             container = aq_parent(aq_inner(uf))
00379             parent = aq_parent(aq_inner(container))
00380             uf = getattr(parent, 'acl_users', None)
00381         return None
00382 
00383     def _huntUser(self, username, context):
00384         """Find user in the hierarchy of userfolders
00385            starting from context
00386         """
00387         uf = self._huntUserFolder(username, context)
00388         if uf is not None:
00389             return uf.getUserById(username)
00390 
00391     def __getPUS(self):
00392         # Gets something we can call getUsers() and getUserNames() on.
00393         acl_users = self.acl_users
00394         if hasattr(acl_users, 'getUsers'):
00395             return acl_users
00396         else:
00397             # This hack works around the absence of getUsers() in LoginManager.
00398             # Gets the PersistentUserSource object that stores our users
00399             for us in acl_users.UserSourcesGroup.objectValues():
00400                 if us.meta_type == 'Persistent User Source':
00401                     return us.__of__(acl_users)
00402 
00403     security.declareProtected(ManageUsers, 'listMemberIds')
00404     def listMemberIds(self):
00405         '''Lists the ids of all members.  This may eventually be
00406         replaced with a set of methods for querying pieces of the
00407         list rather than the entire list at once.
00408         '''
00409         user_folder = self.__getPUS()
00410         return [ x.getId() for x in user_folder.getUsers() ]
00411 
00412     security.declareProtected(ManageUsers, 'listMembers')
00413     def listMembers(self):
00414         '''Gets the list of all members.
00415         '''
00416         return map(self.wrapUser, self.__getPUS().getUsers())
00417 
00418     security.declareProtected(ListPortalMembers, 'searchMembers')
00419     def searchMembers( self, search_param, search_term ):
00420         """ Search the membership """
00421         # XXX: this method violates the rules for tools/utilities:
00422         # it depends on a non-utility tool
00423         md = getToolByName( self, 'portal_memberdata' )
00424 
00425         return md.searchMemberData( search_param, search_term )
00426 
00427     security.declareProtected(View, 'getCandidateLocalRoles')
00428     def getCandidateLocalRoles(self, obj):
00429         """ What local roles can I assign?
00430         """
00431         member = self.getAuthenticatedMember()
00432         member_roles = member.getRolesInContext(obj)
00433         if _checkPermission(ManageUsers, obj):
00434             local_roles = self.getPortalRoles()
00435             if 'Manager' not in member_roles:
00436                  local_roles.remove('Manager')
00437         else:
00438             local_roles = [ role for role in member_roles
00439                             if role not in ('Member', 'Authenticated') ]
00440         local_roles.sort()
00441         return tuple(local_roles)
00442 
00443     security.declareProtected(View, 'setLocalRoles')
00444     @postonly
00445     def setLocalRoles(self, obj, member_ids, member_role, reindex=1,
00446                       REQUEST=None):
00447         """ Add local roles on an item.
00448         """
00449         if ( _checkPermission(ChangeLocalRoles, obj)
00450              and member_role in self.getCandidateLocalRoles(obj) ):
00451             for member_id in member_ids:
00452                 roles = list(obj.get_local_roles_for_userid( userid=member_id ))
00453 
00454                 if member_role not in roles:
00455                     roles.append( member_role )
00456                     obj.manage_setLocalRoles( member_id, roles )
00457 
00458         if reindex:
00459             # It is assumed that all objects have the method
00460             # reindexObjectSecurity, which is in CMFCatalogAware and
00461             # thus PortalContent and PortalFolder.
00462             obj.reindexObjectSecurity()
00463 
00464     security.declareProtected(View, 'deleteLocalRoles')
00465     @postonly
00466     def deleteLocalRoles(self, obj, member_ids, reindex=1, recursive=0,
00467                          REQUEST=None):
00468         """ Delete local roles of specified members.
00469         """
00470         if _checkPermission(ChangeLocalRoles, obj):
00471             for member_id in member_ids:
00472                 if obj.get_local_roles_for_userid(userid=member_id):
00473                     obj.manage_delLocalRoles(userids=member_ids)
00474                     break
00475 
00476         if recursive and hasattr( aq_base(obj), 'contentValues' ):
00477             for subobj in obj.contentValues():
00478                 self.deleteLocalRoles(subobj, member_ids, 0, 1)
00479 
00480         if reindex:
00481             # reindexObjectSecurity is always recursive
00482             obj.reindexObjectSecurity()
00483 
00484     security.declarePrivate('addMember')
00485     def addMember(self, id, password, roles, domains, properties=None):
00486         '''Adds a new member to the user folder.  Security checks will have
00487         already been performed.  Called by portal_registration.
00488         '''
00489         acl_users = self.acl_users
00490         if hasattr(acl_users, '_doAddUser'):
00491             acl_users._doAddUser(id, password, roles, domains)
00492         else:
00493             # The acl_users folder is a LoginManager.  Search for a UserSource
00494             # with the needed support.
00495             for source in acl_users.UserSourcesGroup.objectValues():
00496                 if hasattr(source, 'addUser'):
00497                     source.__of__(self).addUser(id, password, roles, domains)
00498             raise "Can't add Member", "No supported UserSources"
00499 
00500         if properties is not None:
00501             member = self.getMemberById(id)
00502             member.setMemberProperties(properties)
00503 
00504     security.declareProtected(ManageUsers, 'deleteMembers')
00505     @postonly
00506     def deleteMembers(self, member_ids, delete_memberareas=1,
00507                       delete_localroles=1, REQUEST=None):
00508         """ Delete members specified by member_ids.
00509         """
00510         # XXX: this method violates the rules for tools/utilities:
00511         # it depends on a non-utility tool
00512 
00513         # Delete members in acl_users.
00514         acl_users = self.acl_users
00515         if _checkPermission(ManageUsers, acl_users):
00516             if isinstance(member_ids, basestring):
00517                 member_ids = (member_ids,)
00518             member_ids = list(member_ids)
00519             for member_id in member_ids[:]:
00520                 if not acl_users.getUserById(member_id, None):
00521                     member_ids.remove(member_id)
00522             try:
00523                 acl_users.userFolderDelUsers(member_ids)
00524             except (AttributeError, NotImplementedError, 'NotImplemented'):
00525                 raise NotImplementedError('The underlying User Folder '
00526                                          'doesn\'t support deleting members.')
00527         else:
00528             raise AccessControl_Unauthorized('You need the \'Manage users\' '
00529                                  'permission for the underlying User Folder.')
00530 
00531         # Delete member data in portal_memberdata.
00532         mdtool = getToolByName(self, 'portal_memberdata', None)
00533         if mdtool is not None:
00534             for member_id in member_ids:
00535                 mdtool.deleteMemberData(member_id)
00536 
00537         # Delete members' home folders including all content items.
00538         if delete_memberareas:
00539             for member_id in member_ids:
00540                  self.deleteMemberArea(member_id)
00541 
00542         # Delete members' local roles.
00543         if delete_localroles:
00544             self.deleteLocalRoles( getUtility(ISiteRoot), member_ids,
00545                                    reindex=1, recursive=1 )
00546 
00547         return tuple(member_ids)
00548 
00549     security.declarePublic('getHomeFolder')
00550     def getHomeFolder(self, id=None, verifyPermission=0):
00551         """Returns a member's home folder object or None.
00552         Set verifyPermission to 1 to return None when the user
00553         doesn't have the View permission on the folder.
00554         """
00555         return None
00556 
00557     security.declarePublic('getHomeUrl')
00558     def getHomeUrl(self, id=None, verifyPermission=0):
00559         """Returns the URL to a member's home folder or None.
00560         Set verifyPermission to 1 to return None when the user
00561         doesn't have the View permission on the folder.
00562         """
00563         return None
00564 
00565 InitializeClass(MembershipTool)