Back to index

plone3  3.1.7
membership.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # PlonePAS - Adapt PluggableAuthService for use in Plone
00004 # Copyright (C) 2005 Enfold Systems, Kapil Thangavelu, et al
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 """
00016 """
00017 
00018 import logging
00019 from DateTime import DateTime
00020 
00021 from Globals import InitializeClass
00022 
00023 from Products.CMFCore.utils import getToolByName
00024 
00025 # for createMemberArea...
00026 from AccessControl import getSecurityManager, ClassSecurityInfo
00027 
00028 from Products.CMFPlone.MembershipTool import MembershipTool as BaseMembershipTool
00029 
00030 from Products.CMFPlone.utils import _createObjectByType
00031 from Products.PlonePAS.utils import cleanId
00032 
00033 from zope.deprecation import deprecate
00034 
00035 logger = logging.getLogger('Plone')
00036 
00037 class MembershipTool(BaseMembershipTool):
00038     """PAS-based customization of MembershipTool.
00039 
00040     Uses CMFPlone's as base.
00041     """
00042 
00043     meta_type = "PlonePAS Membership Tool"
00044     toolicon = 'tool.gif'
00045     security = ClassSecurityInfo()
00046 
00047     user_search_keywords = ('login', 'fullname', 'email', 'exact_match',
00048                             'sort_by', 'max_results')
00049 
00050     _properties = (getattr(BaseMembershipTool, '_properties', ()) +
00051                    ({'id': 'user_search_keywords',
00052                      'type': 'lines',
00053                      'mode': 'rw',
00054                      },))
00055 
00056     security.declarePrivate('addMember')
00057     def addMember(self, id, password, roles, domains, properties=None):
00058         """Adds a new member to the user folder.
00059 
00060         Security checks will have already been performed.  Called by
00061         portal_registration.  This one specific to PAS. PAS ignores
00062         domains. Adding members with login_name also not yet
00063         supported.
00064         """
00065         acl_users = self.acl_users
00066         acl_users._doAddUser(id, password, roles, domains)
00067 
00068         if properties is not None:
00069             member = self.getMemberById(id)
00070             member.setMemberProperties(properties)
00071 
00072     security.declarePublic('searchForMembers')
00073     @deprecate("portal_membership.searchForMembers is deprecated and will "
00074                "be removed in Plone 3.5. Use PAS searchUsers instead")
00075     def searchForMembers(self, REQUEST=None, **kw):
00076         """Hacked up version of Plone searchForMembers.
00077 
00078         The following properties can be provided:
00079         - name
00080         - email
00081         - last_login_time
00082         - before_specified_time
00083         - roles (any role will cause a match)
00084         - groupname
00085 
00086         This is an 'AND' request.
00087 
00088         When it takes 'name' as keyword (or in REQUEST) it  searches on
00089         Full name and id.
00090 
00091         Simple name searches are "fast".
00092         """
00093         logger.debug('searchForMembers: started.')
00094 
00095         acl_users = getToolByName(self, "acl_users")
00096         md = getToolByName(self, "portal_memberdata")
00097         groups_tool = getToolByName(self, "portal_groups")
00098 
00099         if REQUEST is not None:
00100             searchmap = REQUEST
00101         else:
00102             searchmap = kw
00103 
00104         # While the parameter is called name it is actually used to search a
00105         # users name, which is stored in the fullname property. We need to fix
00106         # that here so the right name is used when calling into PAS plugins.
00107         if 'name' in searchmap:
00108             searchmap['fullname'] = searchmap['name']
00109             del searchmap['name']
00110 
00111         user_search = dict([ x for x in searchmap.items() 
00112                                 if x[0] in self.user_search_keywords and x[1]])
00113 
00114         fullname = searchmap.get('fullname', None)
00115         email = searchmap.get('email', None)
00116         roles = searchmap.get('roles', None)
00117         last_login_time = searchmap.get('last_login_time', None)
00118         before_specified_time = searchmap.get('before_specified_time', None)
00119         groupname = searchmap.get('groupname', '').strip()
00120         is_manager = self.checkPermission('Manage portal', self)
00121 
00122         if fullname:
00123             fullname = fullname.strip().lower()
00124         if not fullname:
00125             fullname = None
00126         if email:
00127             email = email.strip().lower()
00128         if not email:
00129             email = None
00130 
00131         uf_users = []
00132 
00133         logger.debug(
00134             'searchForMembers: searching PAS '
00135             'with arguments %r.' % user_search)
00136         for user in acl_users.searchUsers(**user_search):
00137             uid = user['userid']
00138             uf_users.append(uid)
00139 
00140         if not uf_users:
00141             return []
00142 
00143         wrap = self.wrapUser
00144         getUserById = acl_users.getUserById
00145 
00146         members = [ getUserById(userid) for userid in set(uf_users)]
00147         members = [ member for member in members if member is not None ]
00148 
00149         if (not email and
00150             not fullname and
00151             not roles and
00152             not groupname and
00153             not last_login_time):
00154             logger.debug(
00155                 'searchForMembers: searching users '
00156                 'with no extra filter, immediate return.')
00157             return members
00158 
00159 
00160         # Now perform individual checks on each user
00161         res = []
00162         portal = getToolByName(self, 'portal_url').getPortalObject()
00163 
00164         for member in members:
00165             if groupname and groupname not in member.getGroupIds():
00166                 continue
00167 
00168             if not (member.getProperty('listed', False) or is_manager):
00169                 continue
00170 
00171             if roles:
00172                 user_roles = member.getRoles()
00173                 found = 0
00174                 for r in roles:
00175                     if r in user_roles:
00176                         found = 1
00177                         break
00178                 if not found:
00179                     continue
00180 
00181             if last_login_time:
00182                 last_login = member.getProperty('last_login_time', '')
00183 
00184                 if isinstance(last_login, basestring):
00185                     # value is a string when member hasn't yet logged in
00186                    last_login = DateTime(last_login or '2000/01/01')
00187                    
00188                 if before_specified_time:
00189                     if last_login >= last_login_time:
00190                         continue
00191                 elif last_login < last_login_time:
00192                     continue
00193 
00194             res.append(member)
00195 
00196         logger.debug('searchForMembers: finished.')
00197         return res
00198 
00199     #############
00200     ## sanitize home folders (we may get URL-illegal ids)
00201 
00202     security.declarePublic('createMemberarea')
00203     def createMemberarea(self, member_id=None, minimal=True):
00204         """
00205         Create a member area for 'member_id' or the authenticated
00206         user, but don't assume that member_id is url-safe.
00207 
00208         Unfortunately, a pretty close copy of the (very large)
00209         original and only a few lines different.  Plone should
00210         probably do this.
00211         """
00212         if not self.getMemberareaCreationFlag():
00213             return None
00214         catalog = getToolByName(self, 'portal_catalog')
00215         membership = getToolByName(self, 'portal_membership')
00216         members = self.getMembersFolder()
00217 
00218         if not member_id:
00219             # member_id is optional (see CMFCore.interfaces.portal_membership:
00220             #     Create a member area for 'member_id' or authenticated user.)
00221             member = membership.getAuthenticatedMember()
00222             member_id = member.getId()
00223 
00224         if hasattr(members, 'aq_explicit'):
00225             members=members.aq_explicit
00226 
00227         if members is None:
00228             # no members area
00229             logger.debug('createMemberarea: members area does not exist.')
00230             return
00231 
00232         safe_member_id = cleanId(member_id)
00233         if hasattr(members, safe_member_id):
00234             # has already this member
00235             logger.debug(
00236                 'createMemberarea: member area '
00237                 'for %r already exists.' % safe_member_id)
00238             return
00239 
00240         if not safe_member_id:
00241             # Could be one of two things:
00242             # - A Emergency User
00243             # - cleanId made a empty string out of member_id
00244             logger.debug(
00245                 'createMemberarea: empty member id '
00246                 '(%r, %r), skipping member area creation.' % (
00247                 member_id, safe_member_id))
00248             return
00249 
00250         _createObjectByType(self.memberarea_type, members, id=safe_member_id)
00251 
00252         # Get the user object from acl_users
00253         acl_users = self.__getPUS()
00254         user = acl_users.getUserById(member_id)
00255         if user is not None:
00256             user = user.__of__(acl_users)
00257         else:
00258             user = getSecurityManager().getUser()
00259             # check that we do not do something wrong
00260             if user.getId() != member_id:
00261                 raise NotImplementedError, \
00262                     'cannot get user for member area creation'
00263 
00264         member_object = self.getMemberById(member_id)
00265 
00266         ## Modify member folder
00267         member_folder = self.getHomeFolder(member_id)
00268         # Grant Ownership and Owner role to Member
00269         member_folder.changeOwnership(user)
00270         member_folder.__ac_local_roles__ = None
00271         member_folder.manage_setLocalRoles(member_id, ['Owner'])
00272         # We use ATCT now use the mutators
00273         fullname = member_object.getProperty('fullname')
00274         member_folder.setTitle(fullname or member_id)
00275         member_folder.reindexObject()
00276 
00277         if not minimal:
00278             ## add homepage text
00279             # get the text from portal_skins automagically
00280             homepageText = getattr(self, 'homePageText', None)
00281             if homepageText:
00282                 portal = getToolByName(self, "portal_url").getPortalObject()
00283                 # call the page template
00284                 content = homepageText(member=member_object, portal=portal).strip()
00285                 _createObjectByType('Document', member_folder, id='index_html')
00286                 hpt = getattr(member_folder, 'index_html')
00287                 # edit title, text and format
00288                 hpt.setTitle(fullname or member_id)
00289                 if hpt.meta_type == 'Document':
00290                     hpt.edit(text_format='structured-text', text=content)
00291                 else:
00292                     hpt.update(text=content)
00293                 hpt.setFormat('structured-text')
00294                 hpt.reindexObject()
00295                 # Grant Ownership and Owner role to Member
00296                 hpt.changeOwnership(user)
00297                 hpt.__ac_local_roles__ = None
00298                 hpt.manage_setLocalRoles(member_id, ['Owner'])
00299 
00300         ## Hook to allow doing other things after memberarea creation.
00301         notify_script = getattr(member_folder, 'notifyMemberAreaCreated', None)
00302         if notify_script is not None:
00303             notify_script()
00304 
00305      # deal with ridiculous API change in CMF
00306     security.declarePublic('createMemberArea')
00307     createMemberArea = createMemberarea
00308 
00309     def _getSafeMemberId(self, id=None):
00310         """Return a safe version of a member id.
00311 
00312         If no id is given return the id for the currently authenticated user.
00313         """
00314 
00315         if id is None:
00316             member = self.getAuthenticatedMember()
00317             if not hasattr(member, 'getMemberId'):
00318                 return None
00319             id = member.getMemberId()
00320 
00321         return cleanId(id)
00322 
00323     security.declarePublic('getHomeFolder')
00324     def getHomeFolder(self, id=None, verifyPermission=0):
00325         """ Return a member's home folder object, or None.
00326 
00327         Specially instrumented for URL-quoted-member-id folder
00328         names.
00329         """
00330         safe_id = self._getSafeMemberId(id)
00331         return BaseMembershipTool.getHomeFolder(self, safe_id, verifyPermission)
00332 
00333 
00334     def getPersonalPortrait(self, id=None, verifyPermission=0):
00335         """Return a members personal portait.
00336 
00337         Modified from CMFPlone version to URL-quote the member id.
00338         """
00339         safe_id = self._getSafeMemberId(id)
00340         return BaseMembershipTool.getPersonalPortrait(self, safe_id, verifyPermission)
00341 
00342 
00343     def deletePersonalPortrait(self, id=None):
00344         """deletes the Portait of a member.
00345 
00346         Modified from CMFPlone version to URL-quote the member id.
00347         """
00348         safe_id = self._getSafeMemberId(id)
00349         return BaseMembershipTool.deletePersonalPortrait(self, safe_id)
00350 
00351 
00352     def changeMemberPortrait(self, portrait, id=None):
00353         """update the portait of a member.
00354 
00355         Modified from CMFPlone version to URL-quote the member id.
00356         """
00357         safe_id = self._getSafeMemberId(id)
00358         return BaseMembershipTool.changeMemberPortrait(self, portrait, safe_id)
00359 
00360 InitializeClass(MembershipTool)
00361