Huge Plinn Folder utilisé à la place de l'historique Plinn Folder.
[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 getToolByName, _checkPermission, _getAuthenticatedUser
39 from utils import formatFullName, translate
40 from Products.CMFDefault.utils import decode
41 from Products.CMFDefault.Document import addDocument
42
43 from sets import Set
44 from types import TupleType
45
46
47 from time import time
48 from logging import getLogger
49 console = getLogger('Plinn.MembershipTool')
50
51
52 class MembershipTool( BaseTool ):
53 """ Implement 'portal_membership' interface using "stock" policies.
54 """
55
56
57 meta_type = 'Plinn Membership Tool'
58
59 manage_options=( ({ 'label' : 'Configuration'
60 , 'action' : 'manage_mapRoles'
61 },) + BaseTool.manage_options[1:])
62
63 security = ClassSecurityInfo()
64
65 security.declareProtected(ManagePortal, 'manage_mapRoles')
66 manage_mapRoles = PageTemplateFile('www/configureMembershipTool', globals(),
67 __name__='manage_mapRoles')
68
69 #
70 # 'portal_membership' interface methods
71 #
72
73 # change security settings for inherited methods
74 security.declareProtected(ListPortalMembers, 'getMemberById')
75
76
77 memberareaPortalType = 'Huge Plinn Folder'
78
79
80 # security.declareProtected(SetOwnPassword, 'setPassword')
81 # def setPassword(self, password, domains=None):
82 # '''Allows the authenticated member to set his/her own password.
83 # '''
84 # user_folder = self.__getPUS()
85 # if user_folder.meta_type == 'Group User Folder' :
86 # registration = getToolByName(self, 'portal_registration', None)
87 # if not self.isAnonymousUser():
88 # member = self.getAuthenticatedMember()
89 # if registration:
90 # failMessage = registration.testPasswordValidity(password)
91 # if failMessage is not None:
92 # raise 'Bad Request', failMessage
93 # member.setSecurityProfile(password=password, domains=domains)
94 # member.changePassword(password)
95 # else:
96 # raise 'Bad Request', 'Not logged in.'
97 #
98 # else :
99 # BaseTool.setPassword(self, password, domains=None)
100
101
102
103 security.declareProtected(ListPortalMembers, 'listMemberIds')
104 def listMemberIds(self):
105 '''Lists the ids of all members. This may eventually be
106 replaced with a set of methods for querying pieces of the
107 list rather than the entire list at once.
108 '''
109 user_folder = self.__getPUS()
110 if user_folder.meta_type == 'Group User Folder' :
111 return user_folder.getPureUserNames()
112 else :
113 return [ x.getId() for x in user_folder.getUsers() ]
114
115
116 security.declareProtected(CheckMemberPermission, 'checkMemberPermission')
117 def checkMemberPermission(self, userid, permissionName, object, subobjectName=None):
118 '''
119 Checks whether the current user has the given permission on
120 the given object or subobject.
121 '''
122 if subobjectName is not None:
123 object = getattr(object, subobjectName)
124
125 return _checkMemberPermission(userid, permissionName, object)
126
127 security.declareProtected(ListPortalMembers, 'listMembers')
128 def listMembers(self):
129 '''Gets the list of all members.
130 '''
131 user_folder = self.__getPUS()
132 if user_folder.meta_type == 'Group User Folder' :
133 return map(self.wrapUser, user_folder.getPureUsers())
134 else :
135 return map(self.wrapUser, user_folder.getUsers())
136
137
138 security.declareProtected(View, 'getCandidateLocalRoles')
139 def getCandidateLocalRoles(self, obj) :
140 """ What local roles can I assign?
141 """
142 member = self.getAuthenticatedMember()
143 valid_roles = obj.valid_roles()
144 if 'Manager' in member.getRoles():
145 local_roles = [r for r in valid_roles if r != 'Anonymous']
146 else:
147 sm = getSecurityManager()
148 allPermissions = self.ac_inherited_permissions(1)
149
150 # construct a dictionary of permissions indexed by role
151 # and get permissions of user in obj context
152 memberPermissions = Set()
153 rolesMappings = {}
154 for role in valid_roles :
155 rolesMappings[role] = Set()
156
157 for p in allPermissions:
158 name, value = p[:2]
159
160 p=Permission(name,value,obj)
161 rolesOfPerm = p.getRoles()
162
163 for role in rolesOfPerm :
164 try : rolesMappings[role].add(name)
165 except KeyError :
166 trName = p._p
167 if hasattr(obj, trName):
168 l = list(getattr(obj, trName))
169 l.remove(role)
170 setattr(obj, trName, tuple(l))
171 msg = '%s role has been removed for %s permission on %s ' % (role, name, obj.absolute_url())
172 #LOG('portal_membership', WARNING, msg)
173
174 parent = obj.aq_inner.aq_parent
175 while type(rolesOfPerm) != TupleType :
176 p=Permission(name, value, parent)
177 rolesOfPerm = p.getRoles()
178 for role in rolesOfPerm :
179 try : rolesMappings[role].add(name)
180 except KeyError : pass
181 try : parent = parent.aq_inner.aq_parent
182 except AttributeError : break
183
184
185 if sm.checkPermission(name, obj) :
186 memberPermissions.add(name)
187
188 local_roles = []
189 for role in valid_roles :
190 if rolesMappings[role] and rolesMappings[role].issubset(memberPermissions) :
191 local_roles.append(role)
192
193 local_roles = [ role for role in local_roles if role not in ('Shared', 'Authenticated', 'Member', 'Anonymous') ]
194 local_roles.sort()
195 return tuple(local_roles)
196
197
198 security.declareProtected(View, 'setLocalRoles')
199 def setLocalRoles( self, obj, member_ids, role, remove=0, reindex=1 ):
200 """ Set local roles on an item """
201 if role not in self.getCandidateLocalRoles(obj) :
202 raise Unauthorized, "You are not allowed to manage %s role" % role
203
204 if self.checkPermission(SetLocalRoles, obj) :
205 if not remove :
206 for member_id in member_ids :
207 # current roles for user id in obj
208 roles = list(obj.get_local_roles_for_userid( userid=member_id ))
209 if role not in roles :
210 roles.append(role)
211 obj.manage_setLocalRoles( member_id, roles)
212 else :
213 for member_id in member_ids :
214 # current roles for user id in obj
215 roles = list(obj.get_local_roles_for_userid( userid=member_id ))
216 try : roles.remove(role)
217 except ValueError : pass
218 else :
219 if len(roles) >= 1 :
220 obj.manage_setLocalRoles( member_id, roles)
221 else :
222 obj.manage_delLocalRoles( userids=[member_id] )
223
224 else :
225 raise Unauthorized
226
227 if reindex:
228 # It is assumed that all objects have the method
229 # reindexObjectSecurity, which is in CMFCatalogAware and
230 # thus PortalContent and PortalFolder.
231 obj.reindexObjectSecurity()
232
233
234 security.declarePublic('getMemberFullNameById')
235 def getMemberFullNameById(self, userid, nameBefore = 1) :
236 """ Return the best formated representation of user fullname. """
237
238 memberFullName = ''
239 if userid and userid != 'No owner' :
240 # No owner is a possible value returned by DefaultDublinCoreImpl.Creator
241 member = self.getMemberById(userid)
242 if not member :
243 return userid
244 memberFullName = member.getMemberFullName(nameBefore=nameBefore)
245
246 return memberFullName
247
248 security.declareProtected(ListPortalMembers, 'getMembers')
249 def getMembers(self, users) :
250 """ Return wraped users """
251 members = []
252 for user in users :
253 members.append(self.getMemberById(user))
254
255 members = filter(None, members)
256 members.sort( lambda m0, m1 : cmp(m0.getMemberSortableFormat(), m1.getMemberSortableFormat()) )
257 return members
258
259
260 security.declareProtected(ListPortalMembers, 'getOtherMembers')
261 def getOtherMembers(self, users) :
262 """ Return members who are not in users list"""
263 allMemberIds = self.listMemberIds()
264 otherMemberIds = [ userId for userId in allMemberIds if userId not in users ]
265 return self.getMembers(otherMemberIds)
266
267
268
269 security.declareProtected(ListPortalMembers, 'getMembersMetadata')
270 def getMembersMetadata(self, users) :
271 """ return metadatas from portal_catalog """
272 userDict = {}
273 for u in users : userDict[u] = True
274 ctool = getToolByName(self, 'portal_catalog')
275 memberBrains = ctool(portal_type='Member Data', sort_on='getMemberSortableFormat')
276 memberList = []
277 complementList = []
278
279 if users :
280 for mb in memberBrains :
281 metadatas = {'id' : mb.getId, 'fullname' : mb.getMemberFullName}
282 if userDict.has_key(mb.getId) :
283 memberList.append(metadatas)
284 else :
285 complementList.append(metadatas)
286 else :
287 complementList = [{'id' : mb.getId, 'fullname' : mb.getMemberFullName} for mb in memberBrains]
288
289 return {'memberList' : memberList, 'complementList' : complementList}
290
291
292
293 security.declareProtected(RemoveMember, 'removeMembers')
294 def removeMembers(self, memberIds = []) :
295 """ remove member
296 """
297 # TODO : remove member document ?
298 mdtool = getToolByName(self, 'portal_memberdata')
299 for m in self.getMembers(memberIds) :
300 m.manage_beforeDelete()
301 mdtool.deleteMemberData(m.getId())
302
303 self.aq_inner.acl_users.deleteUsers(users = memberIds)
304
305
306
307 security.declareProtected(ManagePortal, 'setMemberAreaPortalType')
308 def setMemberAreaPortalType(self, member_folder_portal_type):
309 """ Set member area portal type to construct."""
310 ttool = getToolByName(self, 'portal_types')
311 if member_folder_portal_type not in ttool.objectIds() :
312 raise ValueError, "Unknown portal type : %s" % str(member_folder_portal_type)
313
314 self.memberareaPortalType = member_folder_portal_type
315 return MessageDialog(title ='Type updated',
316 message='The member area type have been updated',
317 action ='manage_mapRoles')
318
319 def getMemberAreaPortalType(self) :
320 return self.memberareaPortalType
321
322
323 def getHomeFolder(self, id=None, verifyPermission=0):
324 """ Return a member's home folder object, or None.
325 """
326 if id is None:
327 member = self.getAuthenticatedMember()
328 if not hasattr(member, 'getMemberId'):
329 return None
330 id = member.getMemberId()
331 members = self.getMembersFolder()
332 if members is not None:
333 if not hasattr(members, id) and getattr(self, 'memberareaCreationFlag', 0) != 0 :
334 self.createMemberArea(id)
335 try:
336 folder = members._getOb(id)
337 if verifyPermission and not _checkPermission(View, folder):
338 # Don't return the folder if the user can't get to it.
339 return None
340 return folder
341 except (AttributeError, TypeError, KeyError):
342 pass
343 return None
344
345 security.declarePublic('createMemberArea')
346 def createMemberArea(self, member_id=''):
347 """ Create a member area for 'member_id' or authenticated user.
348 """
349 if not self.getMemberareaCreationFlag():
350 return None
351 members = self.getMembersFolder()
352 if not members:
353 return None
354 if self.isAnonymousUser():
355 return None
356 # Note: We can't use getAuthenticatedMember() and getMemberById()
357 # because they might be wrapped by MemberDataTool.
358 user = _getAuthenticatedUser(self)
359 user_id = user.getId()
360 if member_id in ('', user_id):
361 member = user
362 member_id = user_id
363 else:
364 if _checkPermission(ManageUsers, self):
365 member = self.acl_users.getUserById(member_id, None)
366 if member:
367 member = member.__of__(self.acl_users)
368 else:
369 raise ValueError, 'Member %s does not exist' % member_id
370 else:
371 return None
372
373 if hasattr( aq_base(members), member_id ):
374 return None
375
376 ttool = getToolByName(self, 'portal_types')
377 info = getattr(ttool, self.memberareaPortalType)
378
379 memberFullName = self.getMemberFullNameById(member_id, nameBefore = 0)
380 f = info._constructInstance( members, member_id, title=memberFullName )
381
382 # Grant Ownership and Owner role to Member
383 f.changeOwnership(user)
384 f.__ac_local_roles__ = None
385 f.manage_setLocalRoles(member_id, ['Owner'])
386
387 f.reindexObjectSecurity()
388
389 # Create Member's initial content.
390 if hasattr(self, 'createMemberContent') :
391 self.createMemberContent(member=user,
392 member_id=member_id,
393 member_folder=f)
394 else :
395 def _(message, context, expand=()) :
396 trmessage = decode(translate(message, context), context)
397 expand = tuple([decode(e, context) for e in expand])
398 return (trmessage % expand).encode('utf-8')
399
400 # Create Member's home page.
401 addDocument( f
402 , 'index_html'
403 , title = _("%s's Home", self, (memberFullName,))
404 , description = _("%s's front page", self, (memberFullName,))
405 , text_format = "html"
406 , text = self.default_member_content(memberFullName=memberFullName).encode('utf-8')
407 )
408
409 # Grant Ownership and Owner role to Member
410 f.index_html.changeOwnership(user)
411 f.index_html.__ac_local_roles__ = None
412 f.index_html.manage_setLocalRoles(member_id, ['Owner'])
413
414 f.index_html._setPortalTypeName( 'Document' )
415
416 # Overcome an apparent catalog bug.
417 f.index_html.reindexObject()
418 wftool = getToolByName( f, 'portal_workflow' )
419 wftool.notifyCreated( f.index_html )
420
421 return f
422
423
424 security.declareProtected(ListPortalMembers, 'looseSearchMembers')
425 def looseSearchMembers(self, searchString) :
426 """ """
427
428 words = searchString.strip().split()
429 words = [word.lower() for word in words]
430
431 mdtool = getToolByName(self, 'portal_memberdata')
432 mdProperties = mdtool.propertyIds()
433 searchableProperties = [ p['id'] for p in mdtool.propertyMap() if p['type'] == 'string' ] + ['id']
434 try : searchableProperties.remove('portal_skin')
435 except ValueError : pass
436
437 match = []
438 for m in self.listMembers() :
439 allWordsMatch = False
440 for word in words :
441 for p in searchableProperties :
442 if str(m.getProperty(p, '')).lower().find(word) != -1 :
443 allWordsMatch = True
444 break
445 else :
446 allWordsMatch = False
447
448 if not allWordsMatch :
449 break
450 else :
451 match.append(m)
452
453 return match
454
455 def __getPUS(self):
456 # CMFCore.MembershipTool.MembershipTool tests 'getUsers' method but :
457 # "enumeration" methods ('getUserNames', 'getUsers') are *not*
458 # part of the contract! See IEnumerableUserFolder.
459 # (from PluggableAuthService.interfaces.authservice #233)
460 return self.acl_users
461
462
463 InitializeClass(MembershipTool)