Merge commit 'a4cb4d96face514924387d34746b3148848ac092' into zope-2.13
[Plinn.git] / MembershipTool.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 BenoƮt PIN <benoit.pin@ensmp.fr> #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Plinn portal_membership
21
22
23
24 """
25
26 from AccessControl import ClassSecurityInfo, getSecurityManager
27 from AccessControl.unauthorized import Unauthorized
28 from AccessControl.SpecialUsers import nobody
29 from AccessControl.Permission import Permission
30 from Acquisition import aq_base, aq_inner
31 from Globals import InitializeClass, MessageDialog
32 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
33
34 from Products.CMFDefault.MembershipTool import MembershipTool as BaseTool
35 from Products.CMFCore.permissions import View, ListPortalMembers, ManagePortal, SetOwnPassword, ChangePermissions
36 from permissions import RemoveMember, SetLocalRoles, CheckMemberPermission
37 from utils import _checkMemberPermission
38 from Products.CMFCore.utils import _checkPermission, _getAuthenticatedUser
39 from Products.CMFCore.utils import getUtilityByInterfaceName
40 from utils import formatFullName, translate
41 from Products.CMFDefault.utils import decode
42 from Products.CMFDefault.Document import addDocument
43
44 from sets import Set
45 from types import TupleType
46
47
48 from time import time
49 from logging import getLogger
50 console = getLogger('Plinn.MembershipTool')
51
52
53 class MembershipTool( BaseTool ):
54 """ Implement 'portal_membership' interface using "stock" policies.
55 """
56
57
58 meta_type = 'Plinn Membership Tool'
59
60 manage_options=( ({ 'label' : 'Configuration'
61 , 'action' : 'manage_mapRoles'
62 },) + BaseTool.manage_options[1:])
63
64 security = ClassSecurityInfo()
65
66 security.declareProtected(ManagePortal, 'manage_mapRoles')
67 manage_mapRoles = PageTemplateFile('www/configureMembershipTool', globals(),
68 __name__='manage_mapRoles')
69
70 #
71 # 'portal_membership' interface methods
72 #
73
74 # change security settings for inherited methods
75 security.declareProtected(ListPortalMembers, 'getMemberById')
76
77
78 memberareaPortalType = 'Huge Plinn Folder'
79
80
81 # security.declareProtected(SetOwnPassword, 'setPassword')
82 # def setPassword(self, password, domains=None):
83 # '''Allows the authenticated member to set his/her own password.
84 # '''
85 # user_folder = self.__getPUS()
86 # if user_folder.meta_type == 'Group User Folder' :
87 # registration = getToolByName(self, 'portal_registration', None)
88 # if not self.isAnonymousUser():
89 # member = self.getAuthenticatedMember()
90 # if registration:
91 # failMessage = registration.testPasswordValidity(password)
92 # if failMessage is not None:
93 # raise 'Bad Request', failMessage
94 # member.setSecurityProfile(password=password, domains=domains)
95 # member.changePassword(password)
96 # else:
97 # raise 'Bad Request', 'Not logged in.'
98 #
99 # else :
100 # BaseTool.setPassword(self, password, domains=None)
101
102
103
104 security.declareProtected(ListPortalMembers, 'listMemberIds')
105 def listMemberIds(self):
106 '''Lists the ids of all members. This may eventually be
107 replaced with a set of methods for querying pieces of the
108 list rather than the entire list at once.
109 '''
110 user_folder = self.__getPUS()
111 if user_folder.meta_type == 'Group User Folder' :
112 return user_folder.getPureUserNames()
113 else :
114 return [ x.getId() for x in user_folder.getUsers() ]
115
116
117 security.declareProtected(CheckMemberPermission, 'checkMemberPermission')
118 def checkMemberPermission(self, userid, permissionName, object, subobjectName=None):
119 '''
120 Checks whether the current user has the given permission on
121 the given object or subobject.
122 '''
123 if subobjectName is not None:
124 object = getattr(object, subobjectName)
125
126 return _checkMemberPermission(userid, permissionName, object)
127
128 security.declareProtected(ListPortalMembers, 'listMembers')
129 def listMembers(self):
130 '''Gets the list of all members.
131 '''
132 user_folder = self.__getPUS()
133 if user_folder.meta_type == 'Group User Folder' :
134 return map(self.wrapUser, user_folder.getPureUsers())
135 else :
136 return map(self.wrapUser, user_folder.getUsers())
137
138
139 security.declareProtected(View, 'getCandidateLocalRoles')
140 def getCandidateLocalRoles(self, obj) :
141 """ What local roles can I assign?
142 """
143 member = self.getAuthenticatedMember()
144 valid_roles = obj.valid_roles()
145 if 'Manager' in member.getRoles():
146 local_roles = [r for r in valid_roles if r != 'Anonymous']
147 else:
148 sm = getSecurityManager()
149 allPermissions = self.ac_inherited_permissions(1)
150
151 # construct a dictionary of permissions indexed by role
152 # and get permissions of user in obj context
153 memberPermissions = Set()
154 rolesMappings = {}
155 for role in valid_roles :
156 rolesMappings[role] = Set()
157
158 for p in allPermissions:
159 name, value = p[:2]
160
161 p=Permission(name,value,obj)
162 rolesOfPerm = p.getRoles()
163
164 for role in rolesOfPerm :
165 try : rolesMappings[role].add(name)
166 except KeyError :
167 trName = p._p
168 if hasattr(obj, trName):
169 l = list(getattr(obj, trName))
170 l.remove(role)
171 setattr(obj, trName, tuple(l))
172 msg = '%s role has been removed for %s permission on %s ' % (role, name, obj.absolute_url())
173 #LOG('portal_membership', WARNING, msg)
174
175 parent = obj.aq_inner.aq_parent
176 while type(rolesOfPerm) != TupleType :
177 p=Permission(name, value, parent)
178 rolesOfPerm = p.getRoles()
179 for role in rolesOfPerm :
180 try : rolesMappings[role].add(name)
181 except KeyError : pass
182 try : parent = parent.aq_inner.aq_parent
183 except AttributeError : break
184
185
186 if sm.checkPermission(name, obj) :
187 memberPermissions.add(name)
188
189 local_roles = []
190 for role in valid_roles :
191 if rolesMappings[role] and rolesMappings[role].issubset(memberPermissions) :
192 local_roles.append(role)
193
194 local_roles = [ role for role in local_roles if role not in ('Shared', 'Authenticated', 'Member', 'Anonymous') ]
195 local_roles.sort()
196 return tuple(local_roles)
197
198
199 security.declareProtected(View, 'setLocalRoles')
200 def setLocalRoles( self, obj, member_ids, role, remove=0, reindex=1 ):
201 """ Set local roles on an item """
202 if role not in self.getCandidateLocalRoles(obj) :
203 raise Unauthorized, "You are not allowed to manage %s role" % role
204
205 if self.checkPermission(SetLocalRoles, obj) :
206 if not remove :
207 for member_id in member_ids :
208 # current roles for user id in obj
209 roles = list(obj.get_local_roles_for_userid( userid=member_id ))
210 if role not in roles :
211 roles.append(role)
212 obj.manage_setLocalRoles( member_id, roles)
213 else :
214 for member_id in member_ids :
215 # current roles for user id in obj
216 roles = list(obj.get_local_roles_for_userid( userid=member_id ))
217 try : roles.remove(role)
218 except ValueError : pass
219 else :
220 if len(roles) >= 1 :
221 obj.manage_setLocalRoles( member_id, roles)
222 else :
223 obj.manage_delLocalRoles( userids=[member_id] )
224
225 else :
226 raise Unauthorized
227
228 if reindex:
229 # It is assumed that all objects have the method
230 # reindexObjectSecurity, which is in CMFCatalogAware and
231 # thus PortalContent and PortalFolder.
232 obj.reindexObjectSecurity()
233
234
235 security.declarePublic('getMemberFullNameById')
236 def getMemberFullNameById(self, userid, nameBefore = 1) :
237 """ Return the best formated representation of user fullname. """
238
239 memberFullName = ''
240 if userid and userid != 'No owner' :
241 # No owner is a possible value returned by DefaultDublinCoreImpl.Creator
242 member = self.getMemberById(userid)
243 if not member :
244 return userid
245 memberFullName = member.getMemberFullName(nameBefore=nameBefore)
246
247 return memberFullName
248
249 security.declareProtected(ListPortalMembers, 'getMembers')
250 def getMembers(self, users) :
251 """ Return wraped users """
252 members = []
253 for user in users :
254 members.append(self.getMemberById(user))
255
256 members = filter(None, members)
257 members.sort( lambda m0, m1 : cmp(m0.getMemberSortableFormat(), m1.getMemberSortableFormat()) )
258 return members
259
260
261 security.declareProtected(ListPortalMembers, 'getOtherMembers')
262 def getOtherMembers(self, users) :
263 """ Return members who are not in users list"""
264 allMemberIds = self.listMemberIds()
265 otherMemberIds = [ userId for userId in allMemberIds if userId not in users ]
266 return self.getMembers(otherMemberIds)
267
268
269
270 security.declareProtected(ListPortalMembers, 'getMembersMetadata')
271 def getMembersMetadata(self, users) :
272 """ return metadata from portal_catalog """
273 userDict = {}
274 for u in users : userDict[u] = True
275 ctool = getUtilityByInterfaceName('Products.CMFCore.interfaces.ICatalogTool')
276 memberBrains = ctool(portal_type='Member Data', sort_on='getMemberSortableFormat')
277 memberList = []
278 complementList = []
279
280 if users :
281 for mb in memberBrains :
282 metadata = {'id' : mb.getId, 'fullname' : mb.getMemberFullName}
283 if userDict.has_key(mb.getId) :
284 memberList.append(metadata)
285 else :
286 complementList.append(metadata)
287 else :
288 complementList = [{'id' : mb.getId, 'fullname' : mb.getMemberFullName} for mb in memberBrains]
289
290 return {'memberList' : memberList, 'complementList' : complementList}
291
292
293
294 security.declareProtected(RemoveMember, 'removeMembers')
295 def removeMembers(self, memberIds = []) :
296 """ remove member
297 """
298 # TODO : remove member document ?
299 mdtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMemberDataTool')
300 for m in self.getMembers(memberIds) :
301 m.manage_beforeDelete()
302 mdtool.deleteMemberData(m.getId())
303
304 self.aq_inner.acl_users.deleteUsers(users = memberIds)
305
306
307
308 security.declareProtected(ManagePortal, 'setMemberAreaPortalType')
309 def setMemberAreaPortalType(self, member_folder_portal_type):
310 """ Set member area portal type to construct."""
311 ttool = getUtilityByInterfaceName('Products.CMFCore.interfaces.ITypesTool')
312 if member_folder_portal_type not in ttool.objectIds() :
313 raise ValueError, "Unknown portal type : %s" % str(member_folder_portal_type)
314
315 self.memberareaPortalType = member_folder_portal_type
316 return MessageDialog(title ='Type updated',
317 message='The member area type have been updated',
318 action ='manage_mapRoles')
319
320 def getMemberAreaPortalType(self) :
321 return self.memberareaPortalType
322
323
324 def getHomeFolder(self, id=None, verifyPermission=0):
325 """ Return a member's home folder object, or None.
326 """
327 if id is None:
328 member = self.getAuthenticatedMember()
329 if not hasattr(member, 'getMemberId'):
330 return None
331 id = member.getMemberId()
332 members = self.getMembersFolder()
333 if members is not None:
334 if not hasattr(members, id) and getattr(self, 'memberareaCreationFlag', 0) != 0 :
335 self.createMemberArea(id)
336 try:
337 folder = members._getOb(id)
338 if verifyPermission and not _checkPermission(View, folder):
339 # Don't return the folder if the user can't get to it.
340 return None
341 return folder
342 except (AttributeError, TypeError, KeyError):
343 pass
344 return None
345
346 security.declarePublic('createMemberArea')
347 def createMemberArea(self, member_id=''):
348 """ Create a member area for 'member_id' or authenticated user.
349 """
350 if not self.getMemberareaCreationFlag():
351 return None
352 members = self.getMembersFolder()
353 if not members:
354 return None
355 if self.isAnonymousUser():
356 return None
357 # Note: We can't use getAuthenticatedMember() and getMemberById()
358 # because they might be wrapped by MemberDataTool.
359 user = _getAuthenticatedUser(self)
360 user_id = user.getId()
361 if member_id in ('', user_id):
362 member = user
363 member_id = user_id
364 else:
365 if _checkPermission(ManageUsers, self):
366 member = self.acl_users.getUserById(member_id, None)
367 if member:
368 member = member.__of__(self.acl_users)
369 else:
370 raise ValueError, 'Member %s does not exist' % member_id
371 else:
372 return None
373
374 if hasattr( aq_base(members), member_id ):
375 return None
376
377 ttool = getUtilityByInterfaceName('Products.CMFCore.interfaces.ITypesTool')
378 info = getattr(ttool, self.memberareaPortalType)
379
380 memberFullName = self.getMemberFullNameById(member_id, nameBefore = 0)
381 f = info._constructInstance( members, member_id, title=memberFullName )
382
383 # Grant Ownership and Owner role to Member
384 f.changeOwnership(user)
385 f.__ac_local_roles__ = None
386 f.manage_setLocalRoles(member_id, ['Owner'])
387
388 f.reindexObjectSecurity()
389 return f
390
391
392 security.declareProtected(ListPortalMembers, 'looseSearchMembers')
393 def looseSearchMembers(self, searchString) :
394 """ """
395
396 words = searchString.strip().split()
397 words = [word.lower() for word in words]
398
399 mdtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMemberDataTool')
400 mdProperties = mdtool.propertyIds()
401 searchableProperties = [ p['id'] for p in mdtool.propertyMap() if p['type'] == 'string' ] + ['id']
402 try : searchableProperties.remove('portal_skin')
403 except ValueError : pass
404
405 match = []
406 for m in self.listMembers() :
407 allWordsMatch = False
408 for word in words :
409 for p in searchableProperties :
410 if str(m.getProperty(p, '')).lower().find(word) != -1 :
411 allWordsMatch = True
412 break
413 else :
414 allWordsMatch = False
415
416 if not allWordsMatch :
417 break
418 else :
419 match.append(m)
420
421 return match
422
423 def __getPUS(self):
424 # CMFCore.MembershipTool.MembershipTool tests 'getUsers' method but :
425 # "enumeration" methods ('getUserNames', 'getUsers') are *not*
426 # part of the contract! See IEnumerableUserFolder.
427 # (from PluggableAuthService.interfaces.authservice #233)
428 return self.acl_users
429
430
431 InitializeClass(MembershipTool)