Back to index

plone3  3.1.7
MembershipTool.py
Go to the documentation of this file.
00001 import PIL
00002 from zope import event
00003 from cStringIO import StringIO
00004 from Products.CMFCore.utils import getToolByName, _checkPermission
00005 from Products.CMFDefault.MembershipTool import MembershipTool as BaseTool
00006 from Products.CMFPlone import ToolNames
00007 from Products.CMFPlone.utils import scale_image
00008 from OFS.Image import Image
00009 from AccessControl import ClassSecurityInfo
00010 from AccessControl.SecurityManagement import getSecurityManager
00011 from Globals import InitializeClass, DTMLFile
00012 from zExceptions import BadRequest
00013 from ZODB.POSException import ConflictError
00014 from AccessControl.SecurityManagement import noSecurityManager
00015 from Acquisition import aq_base, aq_parent, aq_inner
00016 from Products.PlonePAS.events import UserLoggedInEvent
00017 from Products.PlonePAS.events import UserInitialLoginInEvent
00018 from Products.PlonePAS.events import UserLoggedOutEvent
00019 from Products.CMFCore.permissions import ManagePortal
00020 from Products.CMFCore.permissions import ManageUsers
00021 from Products.CMFCore.permissions import SetOwnProperties
00022 from Products.CMFCore.permissions import SetOwnPassword
00023 from Products.CMFCore.permissions import View
00024 from Products.CMFPlone.PloneBaseTool import PloneBaseTool
00025 from AccessControl.requestmethod import postonly
00026 
00027 default_portrait = 'defaultUser.gif'
00028 
00029 class MembershipTool(PloneBaseTool, BaseTool):
00030 
00031     meta_type = ToolNames.MembershipTool
00032     toolicon = 'skins/plone_images/user.gif'
00033     plone_tool = 1
00034     personal_id = '.personal'
00035     portrait_id = 'MyPortrait'
00036     default_portrait = 'defaultUser.gif'
00037     memberarea_type = 'Folder'
00038     security = ClassSecurityInfo()
00039 
00040     __implements__ = (PloneBaseTool.__implements__, BaseTool.__implements__, )
00041 
00042     manage_options = (BaseTool.manage_options +
00043                       ( { 'label' : 'Portraits'
00044                      , 'action' : 'manage_portrait_fix'
00045                      },))
00046 
00047     # TODO I'm not quite sure why getPortalRoles is declared 'Managed'
00048     #    in CMFCore.MembershipTool - but in Plone we are not so anal ;-)
00049     security.declareProtected(View, 'getPortalRoles')
00050 
00051     security.declareProtected(ManagePortal, 'manage_mapRoles')
00052     manage_mapRoles = DTMLFile('www/membershipRolemapping', globals())
00053 
00054     security.declareProtected(ManagePortal, 'manage_portrait_fix')
00055     manage_portrait_fix = DTMLFile('www/portrait_fix', globals())
00056 
00057     security.declareProtected(ManagePortal, 'manage_setMemberAreaType')
00058     def manage_setMemberAreaType(self, type_name, REQUEST=None):
00059         """ ZMI method to set the home folder type by its type name.
00060         """
00061         self.setMemberAreaType(type_name)
00062         if REQUEST is not None:
00063             REQUEST['RESPONSE'].redirect(self.absolute_url()
00064                     + '/manage_mapRoles'
00065                     + '?manage_tabs_message=Member+area+type+changed.')
00066 
00067     security.declareProtected(ManagePortal, 'setMemberAreaType')
00068     def setMemberAreaType(self, type_name):
00069         """ Sets the portal type to use for new home folders.
00070         """
00071         # No check for folderish since someone somewhere may actually want
00072         # members to have objects instead of folders as home "directory".
00073         self.memberarea_type = str(type_name).strip()
00074 
00075     security.declarePublic('getMemberInfo')
00076     def getMemberInfo(self, memberId=None):
00077         """
00078         Return 'harmless' Memberinfo of any member, such as Full name,
00079         Location, etc
00080         """
00081         if not memberId:
00082             member = self.getAuthenticatedMember()
00083         else:
00084             member = self.getMemberById(memberId)
00085 
00086         if member is None:
00087             return None
00088 
00089         memberinfo = { 'fullname'    : member.getProperty('fullname'),
00090                        'description' : member.getProperty('description'),
00091                        'location'    : member.getProperty('location'),
00092                        'language'    : member.getProperty('language'),
00093                        'home_page'   : member.getProperty('home_page'),
00094                        'username'    : member.getUserName(),
00095                      }
00096 
00097         return memberinfo
00098 
00099     security.declarePublic('getPersonalPortrait')
00100     def getPersonalPortrait(self, member_id = None, verifyPermission=0):
00101         """
00102         returns the Portrait for a member_id
00103         """
00104         membertool   = getToolByName(self, 'portal_memberdata')
00105 
00106         if not member_id:
00107             member_id = self.getAuthenticatedMember().getId()
00108 
00109         portrait = membertool._getPortrait(member_id)
00110         if type(portrait) == type(''):
00111             portrait = None
00112         if portrait is not None:
00113             if verifyPermission and not _checkPermission('View', portrait):
00114                 # Don't return the portrait if the user can't get to it
00115                 portrait = None
00116         if portrait is None:
00117             portal = getToolByName(self, 'portal_url').getPortalObject()
00118             portrait = getattr(portal, default_portrait)
00119 
00120         return portrait
00121 
00122     security.declareProtected(SetOwnProperties, 'deletePersonalPortrait') 
00123     def deletePersonalPortrait(self, member_id = None):
00124         """
00125         deletes the Portrait of member_id
00126         """
00127         membertool   = getToolByName(self, 'portal_memberdata')
00128 
00129         if not member_id:
00130             member_id = self.getAuthenticatedMember().getId()
00131 
00132         membertool._deletePortrait(member_id)
00133 
00134     security.declarePublic('getHomeFolder')
00135     def getHomeFolder(self, id=None, verifyPermission=0):
00136         """ Return a member's home folder object, or None.
00137         dwm: straight from CMF1.5.2
00138         """
00139         if id is None:
00140             member = self.getAuthenticatedMember()
00141             if not hasattr(member, 'getMemberId'):
00142                 return None
00143             id = member.getMemberId()
00144         members = self.getMembersFolder()
00145         if members:
00146             try:
00147                 folder = members._getOb(id)
00148                 if verifyPermission and not _checkPermission(View, folder):
00149                     # Don't return the folder if the user can't get to it.
00150                     return None
00151                 return folder
00152             # KeyError added to deal with btree member folders
00153             except (AttributeError, KeyError, TypeError):
00154                 pass
00155         return None
00156 
00157     security.declarePublic('getPersonalFolder')
00158     def getPersonalFolder(self, member_id=None):
00159         """
00160         returns the Personal Item folder for a member
00161         if no Personal Folder exists will return None
00162         """
00163         home=self.getHomeFolder(member_id)
00164         personal=None
00165         if home:
00166             personal=getattr( home
00167                             , self.personal_id
00168                             , None )
00169         return personal
00170 
00171     security.declareProtected(SetOwnProperties, 'changeMemberPortrait')
00172     def changeMemberPortrait(self, portrait, member_id=None):
00173         """
00174         given a portrait we will modify the users portrait
00175         we put this method here because we do not want
00176         .personal or portrait in the catalog
00177         """
00178         if not member_id:
00179             member_id = self.getAuthenticatedMember().getId()
00180 
00181         if portrait and portrait.filename:
00182             scaled, mimetype = scale_image(portrait)
00183             portrait = Image(id=member_id, file=scaled, title='')
00184             membertool   = getToolByName(self, 'portal_memberdata')
00185             membertool._setPortrait(portrait, member_id)
00186 
00187     security.declareProtected(ManageUsers, 'listMembers')
00188     def listMembers(self):
00189         '''Gets the list of all members.
00190         THIS METHOD MIGHT BE VERY EXPENSIVE ON LARGE USER FOLDERS AND MUST BE USED
00191         WITH CARE! We plan to restrict its use in the future (ie. force large requests
00192         to use searchForMembers instead of listMembers, so that it will not be
00193         possible anymore to have a method returning several hundred of users :)
00194         '''
00195         uf = self.acl_users
00196         if uf.meta_type == 'Group User Folder':
00197             return [BaseTool.wrapUser(self, x) for x in uf.getPureUsers()]
00198         else:
00199             return BaseTool.listMembers(self)
00200 
00201     security.declareProtected(ManageUsers, 'listMemberIds')
00202     def listMemberIds(self):
00203         '''Lists the ids of all members.  This may eventually be
00204         replaced with a set of methods for querying pieces of the
00205         list rather than the entire list at once.
00206         '''
00207         uf = self.acl_users
00208         if hasattr(aq_base(uf), 'getPureUserIds'): # GRUF
00209             return uf.getPureUserIds()
00210         else:
00211             return self.__getPUS().getUserIds()
00212 
00213     security.declareProtected(SetOwnPassword, 'testCurrentPassword')
00214     def testCurrentPassword(self, password):
00215         """ test to see if password is current """
00216         REQUEST=getattr(self, 'REQUEST', {})
00217         userid=self.getAuthenticatedMember().getUserId()
00218         acl_users = self._findUsersAclHome(userid)
00219         if not acl_users:
00220             return 0
00221         return acl_users.authenticate(userid, password, REQUEST)
00222 
00223     def _findUsersAclHome(self, userid):
00224         portal = getToolByName(self, 'portal_url').getPortalObject()
00225         acl_users=portal.acl_users
00226         parent = acl_users
00227         while parent:
00228             if acl_users.aq_explicit.getUserById(userid, None) is not None:
00229                 break
00230             parent = aq_parent(aq_inner(parent)).aq_parent
00231             acl_users = getattr(parent, 'acl_users')
00232         if parent:
00233             return acl_users
00234         else:
00235             return None
00236 
00237     security.declareProtected(SetOwnPassword, 'setPassword')
00238     def setPassword(self, password, domains=None, REQUEST=None):
00239         '''Allows the authenticated member to set his/her own password.
00240         '''
00241         registration = getToolByName(self, 'portal_registration', None)
00242         if not self.isAnonymousUser():
00243             member = self.getAuthenticatedMember()
00244             acl_users = self._findUsersAclHome(member.getUserId())#self.acl_users
00245             if not acl_users:
00246                 # should not possibly ever happen
00247                 raise BadRequest, 'did not find current user in any user folder'
00248             if registration:
00249                 failMessage = registration.testPasswordValidity(password)
00250                 if failMessage is not None:
00251                     raise BadRequest, failMessage
00252 
00253             if domains is None:
00254                 domains = []
00255             user = acl_users.getUserById(member.getUserId(), None)
00256             # we must change the users password trough grufs changepassword
00257             # to keep her  group settings
00258             if hasattr(user, 'changePassword'):
00259                 user.changePassword(password)
00260             else:
00261                 acl_users._doChangeUser(member.getUserId(), password, member.getRoles(), domains)
00262             self.credentialsChanged(password, REQUEST=REQUEST)
00263         else:
00264             raise BadRequest, 'Not logged in.'
00265     setPassword = postonly(setPassword)
00266 
00267     security.declareProtected(View, 'getCandidateLocalRoles')
00268     def getCandidateLocalRoles(self, obj):
00269         """ What local roles can I assign?
00270             Override the CMFCore version so that we can see the local roles on
00271             an object, and so that local managers can assign all roles locally.
00272         """
00273         member = self.getAuthenticatedMember()
00274         # Use getRolesInContext as someone may be a local manager
00275         if 'Manager' in member.getRolesInContext(obj):
00276             # Use valid_roles as we may want roles defined only on a subobject
00277             local_roles = [r for r in obj.valid_roles() if r not in
00278                             ('Anonymous', 'Authenticated', 'Shared')]
00279         else:
00280             local_roles = [ role for role in member.getRolesInContext(obj)
00281                             if role not in ('Member', 'Authenticated') ]
00282         local_roles.sort()
00283         return tuple(local_roles)
00284 
00285 
00286     security.declareProtected(View, 'loginUser')
00287     def loginUser(self, REQUEST=None):
00288         """ Handle a login for the current user.
00289 
00290         This method takes care of all the standard work that needs to be
00291         done when a user logs in:
00292         - clear the copy/cut/paste clipboard
00293         - PAS credentials update
00294         - sending a logged-in event
00295         - storing the login time
00296         - create the member area if it does not exist
00297         """
00298         user=getSecurityManager().getUser()
00299         if user is None:
00300             return
00301 
00302         if self.setLoginTimes():
00303             event.notify(UserInitialLoginInEvent(user))
00304         else:
00305             event.notify(UserLoggedInEvent(user))
00306 
00307         if REQUEST is None:
00308             REQUEST=getattr(self, 'REQUEST', None)
00309         if REQUEST is None:
00310             return
00311 
00312         # Expire the clipboard
00313         if REQUEST.get('__cp', None) is not None:
00314             REQUEST.RESPONSE.expireCookie('__cp', path='/')
00315 
00316         self.createMemberArea()
00317 
00318         try:
00319             pas = getToolByName(self, 'acl_users')
00320             pas.credentials_cookie_auth.login()
00321         except AttributeError:
00322             # The cookie plugin may not be present
00323             pass
00324 
00325 
00326     security.declareProtected(View, 'logoutUser')
00327     def logoutUser(self, REQUEST=None):
00328         """Process a user logout.
00329 
00330         This takes care of all the standard logout work:
00331         - ask the user folder to logout
00332         - expire a skin selection cookie
00333         - invalidate a Zope session if there is one
00334         """
00335         # Invalidate existing sessions, but only if they exist.
00336         sdm = getToolByName(self, 'session_data_manager', None)
00337         if sdm is not None:
00338                 session = sdm.getSessionData(create=0)
00339                 if session is not None:
00340                             session.invalidate()
00341 
00342         if REQUEST is None:
00343             REQUEST=getattr(self, 'REQUEST', None)
00344         if REQUEST is not None:
00345             pas = getToolByName(self, 'acl_users')
00346             try:
00347                 pas.logout(REQUEST)
00348             except:
00349                 # XXX Bare except copied from logout.cpy. This should be
00350                 # changed in the next Plone release.
00351                 pass
00352 
00353             # Expire the skin cookie if it is not configured to persist
00354             st = getToolByName(self, "portal_skins")
00355             skinvar = st.getRequestVarname()
00356             if REQUEST.has_key(skinvar) and not st.getCookiePersistence():
00357                     portal = getToolByName(self, "portal_url").getPortalObject()
00358                     path = '/' + portal.absolute_url(1)
00359                     # XXX check if this path is sane
00360                     REQUEST.RESPONSE.expireCookie(skinvar, path=path)
00361 
00362         user=getSecurityManager().getUser()
00363         if user is not None:
00364             event.notify(UserLoggedOutEvent(user))
00365 
00366     security.declareProtected(View, 'immediateLogout')
00367     def immediateLogout(self):
00368         """ Log the current user out immediately.  Used by logout.py so that
00369             we do not have to do a redirect to show the logged out status. """
00370         noSecurityManager()
00371 
00372     security.declarePublic('setLoginTimes')
00373     def setLoginTimes(self):
00374         """ Called by logged_in to set the login time properties
00375             even if members lack the "Set own properties" permission.
00376 
00377             The return value indicates if this is the first logged
00378             login time.
00379         """
00380         res=False
00381         if not self.isAnonymousUser():
00382             member = self.getAuthenticatedMember()
00383             login_time = member.getProperty('login_time', '2000/01/01')
00384             if str(login_time) == '2000/01/01':
00385                 res=True
00386                 login_time = self.ZopeTime()
00387             member.setProperties(login_time=self.ZopeTime(),
00388                                  last_login_time=login_time)
00389         return res
00390 
00391     security.declareProtected(ManagePortal, 'getBadMembers')
00392     def getBadMembers(self):
00393         """Will search for members with bad images in the portal_memberdata
00394         delete their portraits and return their member ids"""
00395         memberdata = getToolByName(self, 'portal_memberdata')
00396         portraits = getattr(memberdata, 'portraits', None)
00397         if portraits is None:
00398             return []
00399         bad_member_ids = []
00400         import transaction
00401         TXN_THRESHOLD = 50
00402         counter = 1
00403         for member_id in tuple(portraits.objectIds()):
00404             portrait = portraits[member_id]
00405             portrait_data = str(portrait.data)
00406             if portrait_data == '':
00407                 continue
00408             try:
00409                 img = PIL.Image.open(StringIO(portrait_data))
00410             except ConflictError:
00411                 pass
00412             except:
00413                 # Anything else we have a bad bad image and we destroy it
00414                 # and ask questions later.
00415                 portraits._delObject(member_id)
00416                 bad_member_ids.append(member_id)
00417             if not counter%TXN_THRESHOLD:
00418                 transaction.savepoint(optimistic=True)
00419             counter = counter + 1
00420 
00421         return bad_member_ids
00422 
00423 MembershipTool.__doc__ = BaseTool.__doc__
00424 
00425 InitializeClass(MembershipTool)