1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 Benoît PIN <benoit.pin@ensmp.fr> #
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. #
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. #
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
22 $Id: MembershipTool.py 1547 2010-01-05 16:24:22Z pin $
23 $URL: http://svn.cri.ensmp.fr/svn/Plinn/branches/CMF-2.1/MembershipTool.py $
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
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
44 from types
import TupleType
48 from logging
import getLogger
49 console
= getLogger('Plinn.MembershipTool')
52 class MembershipTool( BaseTool
):
53 """ Implement 'portal_membership' interface using "stock" policies.
57 meta_type
= 'Plinn Membership Tool'
59 manage_options
=( ({ 'label' : 'Configuration'
60 , 'action' : 'manage_mapRoles'
61 },) + BaseTool
.manage_options
[1:])
63 security
= ClassSecurityInfo()
65 security
.declareProtected(ManagePortal
, 'manage_mapRoles')
66 manage_mapRoles
= PageTemplateFile('www/configureMembershipTool', globals(),
67 __name__
='manage_mapRoles')
70 # 'portal_membership' interface methods
73 # change security settings for inherited methods
74 security
.declareProtected(ListPortalMembers
, 'getMemberById')
77 memberareaPortalType
= 'Folder'
80 security
.declareProtected(SetOwnPassword
, 'setPassword')
81 def setPassword(self
, password
, domains
=None):
82 '''Allows the authenticated member to set his/her own password.
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()
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
)
96 raise 'Bad Request', 'Not logged in.'
99 BaseTool
.setPassword(self
, password
, domains
=None)
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.
109 user_folder
= self
.__getPUS
()
110 if user_folder
.meta_type
== 'Group User Folder' :
111 return user_folder
.getPureUserNames()
113 return [ x
.getId() for x
in user_folder
.getUsers() ]
116 security
.declareProtected(CheckMemberPermission
, 'checkMemberPermission')
117 def checkMemberPermission(self
, userid
, permissionName
, object, subobjectName
=None):
119 Checks whether the current user has the given permission on
120 the given object or subobject.
122 if subobjectName
is not None:
123 object = getattr(object, subobjectName
)
125 return _checkMemberPermission(userid
, permissionName
, object)
127 security
.declareProtected(ListPortalMembers
, 'listMembers')
128 def listMembers(self
):
129 '''Gets the list of all members.
131 user_folder
= self
.__getPUS
()
132 if user_folder
.meta_type
== 'Group User Folder' :
133 return map(self
.wrapUser
, user_folder
.getPureUsers())
135 return map(self
.wrapUser
, user_folder
.getUsers())
138 security
.declareProtected(View
, 'getCandidateLocalRoles')
139 def getCandidateLocalRoles(self
, obj
) :
140 """ What local roles can I assign?
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']
147 sm
= getSecurityManager()
148 allPermissions
= self
.ac_inherited_permissions(1)
150 # construct a dictionary of permissions indexed by role
151 # and get permissions of user in obj context
152 memberPermissions
= Set()
154 for role
in valid_roles
:
155 rolesMappings
[role
] = Set()
157 for p
in allPermissions
:
160 p
=Permission(name
,value
,obj
)
161 rolesOfPerm
= p
.getRoles()
163 for role
in rolesOfPerm
:
164 try : rolesMappings
[role
].add(name
)
167 if hasattr(obj
, trName
):
168 l
= list(getattr(obj
, trName
))
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)
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
185 if sm
.checkPermission(name
, obj
) :
186 memberPermissions
.add(name
)
189 for role
in valid_roles
:
190 if rolesMappings
[role
] and rolesMappings
[role
].issubset(memberPermissions
) :
191 local_roles
.append(role
)
193 local_roles
= [ role
for role
in local_roles
if role
not in ('Shared', 'Authenticated', 'Member', 'Anonymous') ]
195 return tuple(local_roles
)
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
204 if self
.checkPermission(SetLocalRoles
, obj
) :
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
:
211 obj
.manage_setLocalRoles( member_id
, roles
)
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
220 obj
.manage_setLocalRoles( member_id
, roles
)
222 obj
.manage_delLocalRoles( userids
=[member_id
] )
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()
234 security
.declarePublic('getMemberFullNameById')
235 def getMemberFullNameById(self
, userid
, nameBefore
= 1) :
236 """ Return the best formated representation of user fullname. """
239 if userid
and userid
!= 'No owner' :
240 # No owner is a possible value returned by DefaultDublinCoreImpl.Creator
241 member
= self
.getMemberById(userid
)
244 memberName
= getattr(member
, 'name', '')
245 memberGivenName
= getattr(member
, 'given_name', '')
247 memberFullName
= formatFullName(memberName
, memberGivenName
, memberId
, nameBefore
= nameBefore
)
249 return memberFullName
251 security
.declareProtected(ListPortalMembers
, 'getMembers')
252 def getMembers(self
, users
) :
253 """ Return wraped users """
256 members
.append(self
.getMemberById(user
))
258 members
= filter(None, members
)
259 members
.sort( lambda m0
, m1
: cmp(m0
.getMemberSortableFormat(), m1
.getMemberSortableFormat()) )
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
)
272 security
.declareProtected(ListPortalMembers
, 'getMembersMetadata')
273 def getMembersMetadata(self
, users
) :
274 """ return metadatas from portal_catalog """
276 for u
in users
: userDict
[u
] = True
277 ctool
= getToolByName(self
, 'portal_catalog')
278 memberBrains
= ctool(portal_type
='Member Data', sort_on
='getMemberSortableFormat')
283 for mb
in memberBrains
:
284 metadatas
= {'id' : mb
.getId
, 'fullname' : mb
.getMemberFullName
}
285 if userDict
.has_key(mb
.getId
) :
286 memberList
.append(metadatas
)
288 complementList
.append(metadatas
)
290 complementList
= [{'id' : mb
.getId
, 'fullname' : mb
.getMemberFullName
} for mb
in memberBrains
]
292 return {'memberList' : memberList
, 'complementList' : complementList
}
296 security
.declareProtected(RemoveMember
, 'removeMembers')
297 def removeMembers(self
, memberIds
= []) :
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())
306 self
.aq_inner
.acl_users
.deleteUsers(users
= memberIds
)
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
)
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')
322 def getMemberAreaPortalType(self
) :
323 return self
.memberareaPortalType
326 def getHomeFolder(self
, id=None, verifyPermission
=0):
327 """ Return a member's home folder object, or None.
330 member
= self
.getAuthenticatedMember()
331 if not hasattr(member
, 'getMemberId'):
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)
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.
344 except (AttributeError, TypeError, KeyError):
348 security
.declarePublic('createMemberArea')
349 def createMemberArea(self
, member_id
=''):
350 """ Create a member area for 'member_id' or authenticated user.
352 if not self
.getMemberareaCreationFlag():
354 members
= self
.getMembersFolder()
357 if self
.isAnonymousUser():
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
):
367 if _checkPermission(ManageUsers
, self
):
368 member
= self
.acl_users
.getUserById(member_id
, None)
370 member
= member
.__of
__(self
.acl_users
)
372 raise ValueError, 'Member %s does not exist' % member_id
376 if hasattr( aq_base(members
), member_id
):
379 ttool
= getToolByName(self
, 'portal_types')
380 info
= getattr(ttool
, self
.memberareaPortalType
)
382 f
= info
._constructInstance
( members
, member_id
)
383 memberFullName
= self
.getMemberFullNameById(member_id
, nameBefore
= 0)
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'])
390 f
.setTitle(memberFullName
)
391 info
._finishConstruction
(f
)
393 # Create Member's initial content.
394 if hasattr(self
, 'createMemberContent') :
395 self
.createMemberContent(member
=user
,
399 def _(message
, context
, expand
=()) :
400 trmessage
= decode(translate(message
, context
), context
)
401 expand
= tuple([decode(e
, context
) for e
in expand
])
402 return (trmessage
% expand
).encode('utf-8')
404 # Create Member's home page.
407 , title
= _("%s's Home", self
, (memberFullName
,))
408 , description
= _("%s's front page", self
, (memberFullName
,))
409 , text_format
= "html"
410 , text
= self
.default_member_content(memberFullName
=memberFullName
).encode('utf-8')
413 # Grant Ownership and Owner role to Member
414 f
.index_html
.changeOwnership(user
)
415 f
.index_html
.__ac
_local
_roles
__ = None
416 f
.index_html
.manage_setLocalRoles(member_id
, ['Owner'])
418 f
.index_html
._setPortalTypeName
( 'Document' )
420 # Overcome an apparent catalog bug.
421 f
.index_html
.reindexObject()
422 wftool
= getToolByName( f
, 'portal_workflow' )
423 wftool
.notifyCreated( f
.index_html
)
428 security
.declareProtected(ListPortalMembers
, 'looseSearchMembers')
429 def looseSearchMembers(self
, searchString
) :
432 words
= searchString
.strip().split()
433 words
= [word
.lower() for word
in words
]
435 mdtool
= getToolByName(self
, 'portal_memberdata')
436 mdProperties
= mdtool
.propertyIds()
437 searchableProperties
= [ p
['id'] for p
in mdtool
.propertyMap() if p
['type'] == 'string' ] + ['id']
438 try : searchableProperties
.remove('portal_skin')
439 except ValueError : pass
442 for m
in self
.listMembers() :
443 allWordsMatch
= False
445 for p
in searchableProperties
:
446 if str(m
.getProperty(p
, '')).lower().find(word
) != -1 :
450 allWordsMatch
= False
452 if not allWordsMatch
:
460 # CMFCore.MembershipTool.MembershipTool tests 'getUsers' method but :
461 # "enumeration" methods ('getUserNames', 'getUsers') are *not*
462 # part of the contract! See IEnumerableUserFolder.
463 # (from PluggableAuthService.interfaces.authservice #233)
464 return self
.acl_users
467 InitializeClass(MembershipTool
)