Maintenance : bugfix, jslint.
[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 memberName = getattr(member, 'name', '')
245 memberGivenName = getattr(member, 'given_name', '')
246 memberId = member.getId()
247 memberFullName = formatFullName(memberName, memberGivenName, memberId, nameBefore = nameBefore)
248
249 return memberFullName
250
251 security.declareProtected(ListPortalMembers, 'getMembers')
252 def getMembers(self, users) :
253 """ Return wraped users """
254 members = []
255 for user in users :
256 members.append(self.getMemberById(user))
257
258 members = filter(None, members)
259 members.sort( lambda m0, m1 : cmp(m0.getMemberSortableFormat(), m1.getMemberSortableFormat()) )
260 return members
261
262
263 security.declareProtected(ListPortalMembers, 'getOtherMembers')
264 def getOtherMembers(self, users) :
265 """ Return members who are not in users list"""
266 allMemberIds = self.listMemberIds()
267 otherMemberIds = [ userId for userId in allMemberIds if userId not in users ]
268 return self.getMembers(otherMemberIds)
269
270
271
272 security.declareProtected(ListPortalMembers, 'getMembersMetadata')
273 def getMembersMetadata(self, users) :
274 """ return metadatas from portal_catalog """
275 userDict = {}
276 for u in users : userDict[u] = True
277 ctool = getToolByName(self, 'portal_catalog')
278 memberBrains = ctool(portal_type='Member Data', sort_on='getMemberSortableFormat')
279 memberList = []
280 complementList = []
281
282 if users :
283 for mb in memberBrains :
284 metadatas = {'id' : mb.getId, 'fullname' : mb.getMemberFullName}
285 if userDict.has_key(mb.getId) :
286 memberList.append(metadatas)
287 else :
288 complementList.append(metadatas)
289 else :
290 complementList = [{'id' : mb.getId, 'fullname' : mb.getMemberFullName} for mb in memberBrains]
291
292 return {'memberList' : memberList, 'complementList' : complementList}
293
294
295
296 security.declareProtected(RemoveMember, 'removeMembers')
297 def removeMembers(self, memberIds = []) :
298 """ remove member
299 """
300 # TODO : remove member document ?
301 mdtool = getToolByName(self, 'portal_memberdata')
302 for m in self.getMembers(memberIds) :
303 m.manage_beforeDelete()
304 mdtool.deleteMemberData(m.getId())
305
306 self.aq_inner.acl_users.deleteUsers(users = memberIds)
307
308
309
310 security.declareProtected(ManagePortal, 'setMemberAreaPortalType')
311 def setMemberAreaPortalType(self, member_folder_portal_type):
312 """ Set member area portal type to construct."""
313 ttool = getToolByName(self, 'portal_types')
314 if member_folder_portal_type not in ttool.objectIds() :
315 raise ValueError, "Unknown portal type : %s" % str(member_folder_portal_type)
316
317 self.memberareaPortalType = member_folder_portal_type
318 return MessageDialog(title ='Type updated',
319 message='The member area type have been updated',
320 action ='manage_mapRoles')
321
322 def getMemberAreaPortalType(self) :
323 return self.memberareaPortalType
324
325
326 def getHomeFolder(self, id=None, verifyPermission=0):
327 """ Return a member's home folder object, or None.
328 """
329 if id is None:
330 member = self.getAuthenticatedMember()
331 if not hasattr(member, 'getMemberId'):
332 return None
333 id = member.getMemberId()
334 members = self.getMembersFolder()
335 if members is not None:
336 if not hasattr(members, id) and getattr(self, 'memberareaCreationFlag', 0) != 0 :
337 self.createMemberArea(id)
338 try:
339 folder = members._getOb(id)
340 if verifyPermission and not _checkPermission(View, folder):
341 # Don't return the folder if the user can't get to it.
342 return None
343 return folder
344 except (AttributeError, TypeError, KeyError):
345 pass
346 return None
347
348 security.declarePublic('createMemberArea')
349 def createMemberArea(self, member_id=''):
350 """ Create a member area for 'member_id' or authenticated user.
351 """
352 if not self.getMemberareaCreationFlag():
353 return None
354 members = self.getMembersFolder()
355 if not members:
356 return None
357 if self.isAnonymousUser():
358 return None
359 # Note: We can't use getAuthenticatedMember() and getMemberById()
360 # because they might be wrapped by MemberDataTool.
361 user = _getAuthenticatedUser(self)
362 user_id = user.getId()
363 if member_id in ('', user_id):
364 member = user
365 member_id = user_id
366 else:
367 if _checkPermission(ManageUsers, self):
368 member = self.acl_users.getUserById(member_id, None)
369 if member:
370 member = member.__of__(self.acl_users)
371 else:
372 raise ValueError, 'Member %s does not exist' % member_id
373 else:
374 return None
375
376 if hasattr( aq_base(members), member_id ):
377 return None
378
379 ttool = getToolByName(self, 'portal_types')
380 info = getattr(ttool, self.memberareaPortalType)
381
382 memberFullName = self.getMemberFullNameById(member_id, nameBefore = 0)
383 f = info._constructInstance( members, member_id, title=memberFullName )
384
385 # Grant Ownership and Owner role to Member
386 f.changeOwnership(user)
387 f.__ac_local_roles__ = None
388 f.manage_setLocalRoles(member_id, ['Owner'])
389
390 f.reindexObjectSecurity()
391
392 # Create Member's initial content.
393 if hasattr(self, 'createMemberContent') :
394 self.createMemberContent(member=user,
395 member_id=member_id,
396 member_folder=f)
397 else :
398 def _(message, context, expand=()) :
399 trmessage = decode(translate(message, context), context)
400 expand = tuple([decode(e, context) for e in expand])
401 return (trmessage % expand).encode('utf-8')
402
403 # Create Member's home page.
404 addDocument( f
405 , 'index_html'
406 , title = _("%s's Home", self, (memberFullName,))
407 , description = _("%s's front page", self, (memberFullName,))
408 , text_format = "html"
409 , text = self.default_member_content(memberFullName=memberFullName).encode('utf-8')
410 )
411
412 # Grant Ownership and Owner role to Member
413 f.index_html.changeOwnership(user)
414 f.index_html.__ac_local_roles__ = None
415 f.index_html.manage_setLocalRoles(member_id, ['Owner'])
416
417 f.index_html._setPortalTypeName( 'Document' )
418
419 # Overcome an apparent catalog bug.
420 f.index_html.reindexObject()
421 wftool = getToolByName( f, 'portal_workflow' )
422 wftool.notifyCreated( f.index_html )
423
424 return f
425
426
427 security.declareProtected(ListPortalMembers, 'looseSearchMembers')
428 def looseSearchMembers(self, searchString) :
429 """ """
430
431 words = searchString.strip().split()
432 words = [word.lower() for word in words]
433
434 mdtool = getToolByName(self, 'portal_memberdata')
435 mdProperties = mdtool.propertyIds()
436 searchableProperties = [ p['id'] for p in mdtool.propertyMap() if p['type'] == 'string' ] + ['id']
437 try : searchableProperties.remove('portal_skin')
438 except ValueError : pass
439
440 match = []
441 for m in self.listMembers() :
442 allWordsMatch = False
443 for word in words :
444 for p in searchableProperties :
445 if str(m.getProperty(p, '')).lower().find(word) != -1 :
446 allWordsMatch = True
447 break
448 else :
449 allWordsMatch = False
450
451 if not allWordsMatch :
452 break
453 else :
454 match.append(m)
455
456 return match
457
458 def __getPUS(self):
459 # CMFCore.MembershipTool.MembershipTool tests 'getUsers' method but :
460 # "enumeration" methods ('getUserNames', 'getUsers') are *not*
461 # part of the contract! See IEnumerableUserFolder.
462 # (from PluggableAuthService.interfaces.authservice #233)
463 return self.acl_users
464
465
466 InitializeClass(MembershipTool)